From 467705d897b882e203dce2dca9ae1063f3a06786 Mon Sep 17 00:00:00 2001 From: Alwin Esch Date: Sun, 31 Mar 2024 14:15:23 +0200 Subject: [PATCH] remove CR from all text files --- README.md | 74 +- .../resource.language.en_gb/strings.po | 874 ++-- .../skin.estuary/xml/channelsettings.xml | 430 +- src/align.h | 294 +- src/anglepch.cpp | 50 +- src/anglepch.h | 648 +-- src/channelsettings.cpp | 2728 ++++++------- src/channelsettings.h | 638 +-- src/compat/bionic/complex.cpp | 170 +- src/compat/faad2-hdc/win32_ver.h | 2 +- src/compat/id/commit.h | 28 +- src/compat/libresample/config.h | 16 +- src/compat/pthread.h | 3210 +++++++-------- src/dabdsp/MathHelper.h | 448 +-- src/dabdsp/Xtan2.cpp | 188 +- src/dabdsp/Xtan2.h | 70 +- src/dabdsp/channels.cpp | 470 +-- src/dabdsp/channels.h | 126 +- src/dabdsp/char.h | 48 +- src/dabdsp/charsets.cpp | 414 +- src/dabdsp/charsets.h | 104 +- src/dabdsp/dab-audio.cpp | 330 +- src/dabdsp/dab-audio.h | 166 +- src/dabdsp/dab-constants.cpp | 1060 ++--- src/dabdsp/dab-constants.h | 498 +-- src/dabdsp/dab-processor.h | 76 +- src/dabdsp/dab-virtual.h | 74 +- src/dabdsp/decode_rs.h | 624 +-- src/dabdsp/decode_rs_char.c | 46 +- src/dabdsp/decoder_adapter.cpp | 350 +- src/dabdsp/decoder_adapter.h | 160 +- src/dabdsp/eep-protection.cpp | 306 +- src/dabdsp/eep-protection.h | 90 +- src/dabdsp/encode_rs.h | 116 +- src/dabdsp/encode_rs_char.c | 30 +- src/dabdsp/energy_dispersal.h | 120 +- src/dabdsp/fec.h | 60 +- src/dabdsp/fft.cpp | 336 +- src/dabdsp/fft.h | 240 +- src/dabdsp/fib-processor.cpp | 2692 ++++++------- src/dabdsp/fib-processor.h | 276 +- src/dabdsp/fic-handler.cpp | 482 +-- src/dabdsp/fic-handler.h | 138 +- src/dabdsp/freq-interleaver.cpp | 184 +- src/dabdsp/freq-interleaver.h | 96 +- src/dabdsp/init_rs.h | 208 +- src/dabdsp/init_rs_char.c | 70 +- src/dabdsp/msc-handler.cpp | 332 +- src/dabdsp/msc-handler.h | 200 +- src/dabdsp/ofdm-decoder.cpp | 572 +-- src/dabdsp/ofdm-decoder.h | 186 +- src/dabdsp/ofdm-processor.cpp | 1302 +++--- src/dabdsp/ofdm-processor.h | 248 +- src/dabdsp/phasereference.cpp | 508 +-- src/dabdsp/phasereference.h | 116 +- src/dabdsp/phasetable.cpp | 378 +- src/dabdsp/phasetable.h | 102 +- src/dabdsp/profiling.cpp | 408 +- src/dabdsp/profiling.h | 204 +- src/dabdsp/protTables.cpp | 114 +- src/dabdsp/protTables.h | 62 +- src/dabdsp/protection.h | 78 +- src/dabdsp/radio-controller.h | 456 +-- src/dabdsp/radio-receiver-options.h | 170 +- src/dabdsp/radio-receiver.cpp | 462 +-- src/dabdsp/radio-receiver.h | 236 +- src/dabdsp/ringbuffer.h | 688 ++-- src/dabdsp/rs-common.h | 52 +- src/dabdsp/tii-decoder.cpp | 780 ++-- src/dabdsp/tii-decoder.h | 212 +- src/dabdsp/uep-protection.cpp | 480 +-- src/dabdsp/uep-protection.h | 100 +- src/dabdsp/viterbi.cpp | 710 ++-- src/dabdsp/viterbi.h | 148 +- src/dabmuxscanner.cpp | 784 ++-- src/dabmuxscanner.h | 380 +- src/dabstream.cpp | 1626 ++++---- src/dabstream.h | 700 ++-- src/database.cpp | 3534 ++++++++--------- src/filedevice.cpp | 630 +-- src/filedevice.h | 288 +- src/fmdsp/datatypes.h | 288 +- src/fmdsp/demodulator.cpp | 630 +-- src/fmdsp/demodulator.h | 278 +- src/fmdsp/downconvert.cpp | 1048 ++--- src/fmdsp/downconvert.h | 322 +- src/fmdsp/fastfir.cpp | 606 +-- src/fmdsp/fastfir.h | 152 +- src/fmdsp/fft.cpp | 2404 +++++------ src/fmdsp/fft.h | 180 +- src/fmdsp/filtercoef.h | 906 ++--- src/fmdsp/fir.cpp | 1134 +++--- src/fmdsp/fir.h | 200 +- src/fmdsp/fmdemod.cpp | 536 +-- src/fmdsp/fmdemod.h | 176 +- src/fmdsp/fractresampler.cpp | 672 ++-- src/fmdsp/fractresampler.h | 130 +- src/fmdsp/iir.cpp | 408 +- src/fmdsp/iir.h | 138 +- src/fmdsp/rbdsconstants.h | 604 +-- src/fmdsp/wfmdemod.cpp | 1734 ++++---- src/fmdsp/wfmdemod.h | 290 +- src/fmstream.cpp | 1198 +++--- src/fmstream.h | 458 +-- src/hddsp/acquire.c | 744 ++-- src/hddsp/acquire.h | 82 +- src/hddsp/bitwriter.h | 86 +- src/hddsp/config.h | 164 +- src/hddsp/conv.h | 76 +- src/hddsp/conv_dec.c | 964 ++--- src/hddsp/conv_gen.h | 248 +- src/hddsp/conv_neon.h | 346 +- src/hddsp/conv_sse.h | 646 +-- src/hddsp/decode.c | 716 ++-- src/hddsp/decode.h | 214 +- src/hddsp/defines.h | 518 +-- src/hddsp/firdecim_q15.c | 316 +- src/hddsp/firdecim_q15.h | 32 +- src/hddsp/frame.c | 1300 +++--- src/hddsp/frame.h | 86 +- src/hddsp/input.c | 604 +-- src/hddsp/input.h | 130 +- src/hddsp/nrsc5.c | 730 ++-- src/hddsp/nrsc5.h | 910 ++--- src/hddsp/output.c | 1358 +++---- src/hddsp/output.h | 236 +- src/hddsp/pids.c | 1242 +++--- src/hddsp/pids.h | 156 +- src/hddsp/private.h | 78 +- src/hddsp/rs_char.h | 104 +- src/hddsp/rs_decode.c | 454 +-- src/hddsp/rs_init.c | 266 +- src/hddsp/strndup.c | 64 +- src/hddsp/sync.c | 1358 +++---- src/hddsp/sync.h | 64 +- src/hddsp/unicode.c | 156 +- src/hddsp/unicode.h | 12 +- src/hdmuxscanner.cpp | 490 +-- src/hdmuxscanner.h | 202 +- src/hdstream.cpp | 1336 +++---- src/hdstream.h | 530 +-- src/id3v1tag.cpp | 832 ++-- src/id3v1tag.h | 278 +- src/id3v2tag.cpp | 1004 ++--- src/id3v2tag.h | 490 +-- src/libusb_exception.cpp | 176 +- src/libusb_exception.h | 150 +- src/muxscanner.h | 188 +- src/rdsdecoder.cpp | 1614 ++++---- src/rdsdecoder.h | 376 +- src/renderingcontrol.h | 376 +- src/scalar_condition.h | 224 +- src/signalmeter.cpp | 550 +-- src/signalmeter.h | 298 +- src/socket_exception.h | 230 +- src/sqlite_exception.cpp | 192 +- src/stdafx.cpp | 50 +- src/stdafx.h | 90 +- src/tcpdevice.h | 406 +- src/uecp.cpp | 246 +- src/uecp.h | 322 +- src/usbdevice.cpp | 1048 ++--- src/usbdevice.h | 362 +- src/win32_exception.cpp | 282 +- src/win32_exception.h | 170 +- src/wxstream.cpp | 1128 +++--- src/wxstream.h | 442 +-- 167 files changed, 40069 insertions(+), 40069 deletions(-) diff --git a/README.md b/README.md index 7690d6f..7f770a8 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,37 @@ -# Realtek RTL2832U RTL-SDR Radio PVR Client - - - -Concept based on [__pvr.rtl.radiofm__](https://github.com/AlwinEsch/pvr.rtl.radiofm) - Copyright (C)2015-2018 Alwin Esch -FM Radio Digital Signal Processing derived from [__CuteSDR__](https://sourceforge.net/projects/cutesdr/) - Copyright (C)2010 Moe Wheatley -Hybrid Digital (HD) Radio Digital Signal Processing derived from [__NRSC5__](https://github.com/theori-io/nrsc5) -Digital Audio Broadcast (DAB) Digital Signal Processing adapted from project [__welle.io__](https://github.com/AlbrechtL/welle.io) - -[![Build and run tests](https://github.com/kodi-pvr/pvr.rtlradio/actions/workflows/build.yml/badge.svg?branch=Omega)](https://github.com/kodi-pvr/pvr.rtlradio/actions/workflows/build.yml) -[![Build Status](https://dev.azure.com/teamkodi/kodi-pvr/_apis/build/status/kodi-pvr.pvr.rtlradio?branchName=Omega)](https://dev.azure.com/teamkodi/kodi-pvr/_build/latest?definitionId=85&branchName=Omega) -[![Build Status](https://jenkins.kodi.tv/view/Addons/job/kodi-pvr/job/pvr.rtlradio/job/Omega/badge/icon)](https://jenkins.kodi.tv/blue/organizations/jenkins/kodi-pvr%2Fpvr.rtlradio/branches/) - -## Build instructions - -When building the addon you have to use the correct branch depending on which version of Kodi you're building against. -For example, if you're building the `Omega` branch of Kodi you should checkout the `Omega` branch of this repository. -Also make sure you follow this README from the branch in question. - -### Linux - -The following instructions assume you will have built Kodi already in the `kodi-build` directory -suggested by the README. - -1. `git clone https://github.com/xbmc/xbmc.git` -2. `git clone https://github.com/kodi-pvr/pvr.rtlradio.git` -3. `cd pvr.rtlradio && mkdir build && cd build` -4. `cmake -DADDONS_TO_BUILD=pvr.rtlradio -DADDON_SRC_PREFIX=../.. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=../../xbmc/kodi-build/addons -DPACKAGE_ZIP=1 ../../xbmc/cmake/addons` -5. `make` - -The addon files will be placed in `../../xbmc/kodi-build/addons` so if you build Kodi from source and run it directly -the addon will be available as a system addon. - -##### Useful links - -* [Kodi's PVR user support] (http://forum.kodi.tv/forumdisplay.php?fid=167) -* [Kodi's PVR development support] (http://forum.kodi.tv/forumdisplay.php?fid=136) +# Realtek RTL2832U RTL-SDR Radio PVR Client + + + +Concept based on [__pvr.rtl.radiofm__](https://github.com/AlwinEsch/pvr.rtl.radiofm) - Copyright (C)2015-2018 Alwin Esch +FM Radio Digital Signal Processing derived from [__CuteSDR__](https://sourceforge.net/projects/cutesdr/) - Copyright (C)2010 Moe Wheatley +Hybrid Digital (HD) Radio Digital Signal Processing derived from [__NRSC5__](https://github.com/theori-io/nrsc5) +Digital Audio Broadcast (DAB) Digital Signal Processing adapted from project [__welle.io__](https://github.com/AlbrechtL/welle.io) + +[![Build and run tests](https://github.com/kodi-pvr/pvr.rtlradio/actions/workflows/build.yml/badge.svg?branch=Omega)](https://github.com/kodi-pvr/pvr.rtlradio/actions/workflows/build.yml) +[![Build Status](https://dev.azure.com/teamkodi/kodi-pvr/_apis/build/status/kodi-pvr.pvr.rtlradio?branchName=Omega)](https://dev.azure.com/teamkodi/kodi-pvr/_build/latest?definitionId=85&branchName=Omega) +[![Build Status](https://jenkins.kodi.tv/view/Addons/job/kodi-pvr/job/pvr.rtlradio/job/Omega/badge/icon)](https://jenkins.kodi.tv/blue/organizations/jenkins/kodi-pvr%2Fpvr.rtlradio/branches/) + +## Build instructions + +When building the addon you have to use the correct branch depending on which version of Kodi you're building against. +For example, if you're building the `Omega` branch of Kodi you should checkout the `Omega` branch of this repository. +Also make sure you follow this README from the branch in question. + +### Linux + +The following instructions assume you will have built Kodi already in the `kodi-build` directory +suggested by the README. + +1. `git clone https://github.com/xbmc/xbmc.git` +2. `git clone https://github.com/kodi-pvr/pvr.rtlradio.git` +3. `cd pvr.rtlradio && mkdir build && cd build` +4. `cmake -DADDONS_TO_BUILD=pvr.rtlradio -DADDON_SRC_PREFIX=../.. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=../../xbmc/kodi-build/addons -DPACKAGE_ZIP=1 ../../xbmc/cmake/addons` +5. `make` + +The addon files will be placed in `../../xbmc/kodi-build/addons` so if you build Kodi from source and run it directly +the addon will be available as a system addon. + +##### Useful links + +* [Kodi's PVR user support] (http://forum.kodi.tv/forumdisplay.php?fid=167) +* [Kodi's PVR development support] (http://forum.kodi.tv/forumdisplay.php?fid=136) diff --git a/pvr.rtlradio/resources/language/resource.language.en_gb/strings.po b/pvr.rtlradio/resources/language/resource.language.en_gb/strings.po index 53f5f0c..43e76f0 100644 --- a/pvr.rtlradio/resources/language/resource.language.en_gb/strings.po +++ b/pvr.rtlradio/resources/language/resource.language.en_gb/strings.po @@ -1,437 +1,437 @@ -# Kodi Media Center language file -# Addon Name: RTL-SDR Radio PVR Client -# Addon id: pvr.rtlradio -# Addon Provider: djp952 -msgid "" -msgstr "" -"Project-Id-Version: Kodi Addons\n" -"Report-Msgid-Bugs-To: alanwww1@kodi.org\n" -"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: Kodi Translation Team\n" -"Language-Team: English (http://www.transifex.com/projects/p/xbmc-addons/language/en/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: en\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -# -# 300XX - Setting categories -# - -msgctxt "#30000" -msgid "Device" -msgstr "" - -msgctxt "#30001" -msgid "Region" -msgstr "" - -msgctxt "#30002" -msgid "FM Radio" -msgstr "" - -msgctxt "#30003" -msgid "HD Radio" -msgstr "" - -msgctxt "#30004" -msgid "Weather Radio" -msgstr "" - -msgctxt "#30005" -msgid "DAB" -msgstr "" - -# -# 301XX - Setting names -# - -msgctxt "#30100" -msgid "Connection type" -msgstr "" - -msgctxt "#30101" -msgid "Device index" -msgstr "" - -msgctxt "#30102" -msgid "rtl_tcp server address" -msgstr "" - -msgctxt "#30103" -msgid "rtl_tcp server port" -msgstr "" - -# 30104 can be reused - -msgctxt "#30105" -msgid "PCM output sample rate" -msgstr "" - -msgctxt "#30106" -msgid "Enable Radio Data System (RDS)" -msgstr "" - -msgctxt "#30107" -msgid "Radio region" -msgstr "" - -msgctxt "#30108" -msgid "Frequency correction calibration value (PPM)" -msgstr "" - -msgctxt "#30109" -msgid "PCM output gain" -msgstr "" - -msgctxt "#30110" -msgid "Input sample rate" -msgstr "" - -msgctxt "#30111" -msgid "Downsample quality" -msgstr "" - -# 30113 can be reused - -msgctxt "#30114" -msgid "Enable HD Radio" -msgstr "" - -msgctxt "#30115" -msgid "Enable DAB" -msgstr "" - -msgctxt "#30116" -msgid "Enable Weather Radio" -msgstr "" - -msgctxt "#30117" -msgid "Prepend channel numbers to channel names" -msgstr "" - -# -# 302XX - Setting values -# - -msgctxt "#30200" -msgid "Universal Serial Bus (USB)" -msgstr "" - -msgctxt "#30201" -msgid "Network (rtl_tcp)" -msgstr "" - -# 30202 can be reused -# 30203 can be reused -# 30204 can be reused - -msgctxt "#30205" -msgid "44.1 KHz" -msgstr "" - -msgctxt "#30206" -msgid "48.0 KHz" -msgstr "" - -msgctxt "#30207" -msgid "1.0 MHz" -msgstr "" - -msgctxt "#30208" -msgid "1.2 MHz" -msgstr "" - -msgctxt "#30209" -msgid "1.4 MHz" -msgstr "" - -msgctxt "#30210" -msgid "1.6 MHz" -msgstr "" - -msgctxt "#30211" -msgid "1.8 MHz" -msgstr "" - -msgctxt "#30212" -msgid "2.0 MHz" -msgstr "" - -msgctxt "#30213" -msgid "2.2 MHz" -msgstr "" - -msgctxt "#30214" -msgid "2.4 MHz" -msgstr "" - -msgctxt "#30216" -msgid "Fast" -msgstr "" - -msgctxt "#30217" -msgid "Standard" -msgstr "" - -msgctxt "#30218" -msgid "Maximum" -msgstr "" - -msgctxt "#30219" -msgid "Not set" -msgstr "" - -msgctxt "#30220" -msgid "World" -msgstr "" - -msgctxt "#30221" -msgid "North America" -msgstr "" - -msgctxt "#30222" -msgid "Europe / Australia" -msgstr "" - -# -# 303XX - Dialog box controls -# - -msgctxt "#30300" -msgid "Enter frequency" -msgstr "" - -msgctxt "#30301" -msgid "Channel settings" -msgstr "" - -msgctxt "#30302" -msgid "Multiplex Settings" -msgstr "" - -msgctxt "#30303" -msgid "Ensemble Settings" -msgstr "" - -msgctxt "#30304" -msgid "Wideband FM" -msgstr "" - -msgctxt "#30305" -msgid "Hybrid Digital (HD)" -msgstr "" - -msgctxt "#30306" -msgid "Digital Audio Broadcast (DAB)" -msgstr "" - -msgctxt "#30307" -msgid "Narrowband FM/VHF" -msgstr "" - -msgctxt "#30308" -msgid "Multiplex name" -msgstr "" - -msgctxt "#30309" -msgid "Ensemble name" -msgstr "" - -msgctxt "#30310" -msgid "Multiplex logo" -msgstr "" - -msgctxt "#30311" -msgid "Ensemble logo" -msgstr "" - -msgctxt "#30312" -msgid "Browse for channel logo" -msgstr "" - -msgctxt "#30313" -msgid "Browse for multiplex logo" -msgstr "" - -msgctxt "#30314" -msgid "Browse for ensemble logo" -msgstr "" - -msgctxt "#30315" -msgid "Select PVR Radio region" -msgstr "" - -msgctxt "#30316" -msgid "World" -msgstr "" - -msgctxt "#30317" -msgid "North America" -msgstr "" - -msgctxt "#30318" -msgid "Europe / Australia" -msgstr "" - -msgctxt "#30319" -msgid "Select multiplex subchannels" -msgstr "" - -msgctxt "#30320" -msgid "Select ensemble subchannels" -msgstr "" - -msgctxt "#30321" -msgid "New multiplex" -msgstr "" - -msgctxt "#30322" -msgid "New ensemble" -msgstr "" - -# -# 304XX - PVR Client implementation -# - -msgctxt "#30400" -msgid "Import channel data" -msgstr "" - -msgctxt "#30401" -msgid "Export channel data" -msgstr "" - -msgctxt "#30402" -msgid "Clear channel data" -msgstr "" - -msgctxt "#30403" -msgid "Select channel export folder" -msgstr "" - -msgctxt "#30404" -msgid "Select channel import file" -msgstr "" - -msgctxt "#30405" -msgid "RTL-SDR device in use" -msgstr "" - -msgctxt "#30407" -msgid "Channel settings" -msgstr "" - -msgctxt "#30408" -msgid "FM channels" -msgstr "" - -msgctxt "#30409" -msgid "HD channels" -msgstr "" - -msgctxt "#30410" -msgid "Weather channels" -msgstr "" - -msgctxt "#30411" -msgid "DAB channels" -msgstr "" - -msgctxt "#30412" -msgid "Select raw input file" -msgstr "" - -msgctxt "#30413" -msgid "Select channel type" -msgstr "" - -msgctxt "#30414" -msgid "FM channel" -msgstr "" - -msgctxt "#30415" -msgid "HD Radio multiplex" -msgstr "" - -msgctxt "#30416" -msgid "DAB ensemble" -msgstr "" - -msgctxt "#30417" -msgid "Weather Radio channel" -msgstr "" - -msgctxt "#30418" -msgid "Select DAB ensemble" -msgstr "" - -# -# 305XX - Setting help text -# - -msgctxt "#30500" -msgid "Specifies the RTL-SDR device connection type. When set to Universal Serial Bus (USB), the device must be connected locally to this system. When set to Network (rtl_tcp), the device must be attached to a system running the rtl_tcp server application." -msgstr "" - -msgctxt "#30501" -msgid "When multiple RTL-SDR devices are connected via Universal Serial Bus (USB), specifies the index of the device to connect to. If only one RTL-SDR device is connected, leave set to the default index 0." -msgstr "" - -msgctxt "#30502" -msgid "Specifies the IPv4 address of the rtl_tcp server where the RTL-SDR device is connected." -msgstr "" - -msgctxt "#30503" -msgid "Specifies the port number that the rtl_tcp server will be listening for client connections. If no port number was specified to rtl_tcp leave set to the default port number 1234." -msgstr "" - -# 30504 can be reused - -msgctxt "#30505" -msgid "Specifies the Digital Signal Processor (DSP) PCM output sample rate." -msgstr "" - -msgctxt "#30506" -msgid "When set to ON detected Radio Data System (RDS) / Radio Broadcast Data System (RBDS) information embedded in the FM signal will be decoded and processed." -msgstr "" - -msgctxt "#30507" -msgid "Specifies the region where the RTL-SDR device is being used. When set to North America, FM Radio will default to the RBDS standard and HD Radio and Weather Radio will be available by default. When set to Europe / Australia, FM Radio will default to the RDS standard and DAB Radio will be enabled by default. When set to World, FM Radio will default to the RDS standard and no other modulations will be enabled by default." -msgstr "" - -msgctxt "#30508" -msgid "Specifies the frequency correction calibration offset to apply to the RTL-SDR device. If the calibration offset for the device is not known, leave set to the default value 0." -msgstr "" - -msgctxt "#30509" -msgid "Specifies the Digital Signal Processor (DSP) PCM output audio gain. Lower gain values will reduce the perceived volume of the audio, whereas higher gain values will increase the perceived volume of the audio." -msgstr "" - -msgctxt "#30510" -msgid "Specifies the input sample rate for the RTL-SDR device. Lower sample rates will improve system performance, whereas higher sample rates will improve audio quality." -msgstr "" - -msgctxt "#30511" -msgid "Specifies the Digital Signal Processor (DSP) downsample quality. When set to Fast, downsampling will be optimized for system performance. When set to Maximum, downsampling will be optimized for audio quality." -msgstr "" - -# 30513 can be reused - -msgctxt "#30514" -msgid "When set to ON new Hybrid Digital (HD) Radio channels may be added and existing HD Radio channels will be available for playback. When set to OFF, HD Radio channels may not be added and existing HD Radio channels will not be available for playback. This option should only be enabled when the RTL-SDR device is in the North America region." -msgstr "" - -msgctxt "#30515" -msgid "When set to ON new Digital Audio Broadcast (DAB) channels may be added and existing DAB channels will be available for playback. When set to OFF, DAB channels may not be added and existing DAB channels will not be available for playback. This option should only be enabled when the RTL-SDR device is in the Europe/Australia region." -msgstr "" - -msgctxt "#30516" -msgid "When set to ON new Weather Radio channels may be added and existing Weather Radio channels will be available for playback. When set to OFF, Weather Radio channels may not be added and existing Weather Radio channels will not be available for playback. This option should only be enabled when the RTL-SDR device is in the North America region." -msgstr "" - -msgctxt "#30517" -msgid "When set to ON the channel number will be prepended to the channel name when reported to Kodi." -msgstr "" +# Kodi Media Center language file +# Addon Name: RTL-SDR Radio PVR Client +# Addon id: pvr.rtlradio +# Addon Provider: djp952 +msgid "" +msgstr "" +"Project-Id-Version: Kodi Addons\n" +"Report-Msgid-Bugs-To: alanwww1@kodi.org\n" +"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Kodi Translation Team\n" +"Language-Team: English (http://www.transifex.com/projects/p/xbmc-addons/language/en/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: en\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +# +# 300XX - Setting categories +# + +msgctxt "#30000" +msgid "Device" +msgstr "" + +msgctxt "#30001" +msgid "Region" +msgstr "" + +msgctxt "#30002" +msgid "FM Radio" +msgstr "" + +msgctxt "#30003" +msgid "HD Radio" +msgstr "" + +msgctxt "#30004" +msgid "Weather Radio" +msgstr "" + +msgctxt "#30005" +msgid "DAB" +msgstr "" + +# +# 301XX - Setting names +# + +msgctxt "#30100" +msgid "Connection type" +msgstr "" + +msgctxt "#30101" +msgid "Device index" +msgstr "" + +msgctxt "#30102" +msgid "rtl_tcp server address" +msgstr "" + +msgctxt "#30103" +msgid "rtl_tcp server port" +msgstr "" + +# 30104 can be reused + +msgctxt "#30105" +msgid "PCM output sample rate" +msgstr "" + +msgctxt "#30106" +msgid "Enable Radio Data System (RDS)" +msgstr "" + +msgctxt "#30107" +msgid "Radio region" +msgstr "" + +msgctxt "#30108" +msgid "Frequency correction calibration value (PPM)" +msgstr "" + +msgctxt "#30109" +msgid "PCM output gain" +msgstr "" + +msgctxt "#30110" +msgid "Input sample rate" +msgstr "" + +msgctxt "#30111" +msgid "Downsample quality" +msgstr "" + +# 30113 can be reused + +msgctxt "#30114" +msgid "Enable HD Radio" +msgstr "" + +msgctxt "#30115" +msgid "Enable DAB" +msgstr "" + +msgctxt "#30116" +msgid "Enable Weather Radio" +msgstr "" + +msgctxt "#30117" +msgid "Prepend channel numbers to channel names" +msgstr "" + +# +# 302XX - Setting values +# + +msgctxt "#30200" +msgid "Universal Serial Bus (USB)" +msgstr "" + +msgctxt "#30201" +msgid "Network (rtl_tcp)" +msgstr "" + +# 30202 can be reused +# 30203 can be reused +# 30204 can be reused + +msgctxt "#30205" +msgid "44.1 KHz" +msgstr "" + +msgctxt "#30206" +msgid "48.0 KHz" +msgstr "" + +msgctxt "#30207" +msgid "1.0 MHz" +msgstr "" + +msgctxt "#30208" +msgid "1.2 MHz" +msgstr "" + +msgctxt "#30209" +msgid "1.4 MHz" +msgstr "" + +msgctxt "#30210" +msgid "1.6 MHz" +msgstr "" + +msgctxt "#30211" +msgid "1.8 MHz" +msgstr "" + +msgctxt "#30212" +msgid "2.0 MHz" +msgstr "" + +msgctxt "#30213" +msgid "2.2 MHz" +msgstr "" + +msgctxt "#30214" +msgid "2.4 MHz" +msgstr "" + +msgctxt "#30216" +msgid "Fast" +msgstr "" + +msgctxt "#30217" +msgid "Standard" +msgstr "" + +msgctxt "#30218" +msgid "Maximum" +msgstr "" + +msgctxt "#30219" +msgid "Not set" +msgstr "" + +msgctxt "#30220" +msgid "World" +msgstr "" + +msgctxt "#30221" +msgid "North America" +msgstr "" + +msgctxt "#30222" +msgid "Europe / Australia" +msgstr "" + +# +# 303XX - Dialog box controls +# + +msgctxt "#30300" +msgid "Enter frequency" +msgstr "" + +msgctxt "#30301" +msgid "Channel settings" +msgstr "" + +msgctxt "#30302" +msgid "Multiplex Settings" +msgstr "" + +msgctxt "#30303" +msgid "Ensemble Settings" +msgstr "" + +msgctxt "#30304" +msgid "Wideband FM" +msgstr "" + +msgctxt "#30305" +msgid "Hybrid Digital (HD)" +msgstr "" + +msgctxt "#30306" +msgid "Digital Audio Broadcast (DAB)" +msgstr "" + +msgctxt "#30307" +msgid "Narrowband FM/VHF" +msgstr "" + +msgctxt "#30308" +msgid "Multiplex name" +msgstr "" + +msgctxt "#30309" +msgid "Ensemble name" +msgstr "" + +msgctxt "#30310" +msgid "Multiplex logo" +msgstr "" + +msgctxt "#30311" +msgid "Ensemble logo" +msgstr "" + +msgctxt "#30312" +msgid "Browse for channel logo" +msgstr "" + +msgctxt "#30313" +msgid "Browse for multiplex logo" +msgstr "" + +msgctxt "#30314" +msgid "Browse for ensemble logo" +msgstr "" + +msgctxt "#30315" +msgid "Select PVR Radio region" +msgstr "" + +msgctxt "#30316" +msgid "World" +msgstr "" + +msgctxt "#30317" +msgid "North America" +msgstr "" + +msgctxt "#30318" +msgid "Europe / Australia" +msgstr "" + +msgctxt "#30319" +msgid "Select multiplex subchannels" +msgstr "" + +msgctxt "#30320" +msgid "Select ensemble subchannels" +msgstr "" + +msgctxt "#30321" +msgid "New multiplex" +msgstr "" + +msgctxt "#30322" +msgid "New ensemble" +msgstr "" + +# +# 304XX - PVR Client implementation +# + +msgctxt "#30400" +msgid "Import channel data" +msgstr "" + +msgctxt "#30401" +msgid "Export channel data" +msgstr "" + +msgctxt "#30402" +msgid "Clear channel data" +msgstr "" + +msgctxt "#30403" +msgid "Select channel export folder" +msgstr "" + +msgctxt "#30404" +msgid "Select channel import file" +msgstr "" + +msgctxt "#30405" +msgid "RTL-SDR device in use" +msgstr "" + +msgctxt "#30407" +msgid "Channel settings" +msgstr "" + +msgctxt "#30408" +msgid "FM channels" +msgstr "" + +msgctxt "#30409" +msgid "HD channels" +msgstr "" + +msgctxt "#30410" +msgid "Weather channels" +msgstr "" + +msgctxt "#30411" +msgid "DAB channels" +msgstr "" + +msgctxt "#30412" +msgid "Select raw input file" +msgstr "" + +msgctxt "#30413" +msgid "Select channel type" +msgstr "" + +msgctxt "#30414" +msgid "FM channel" +msgstr "" + +msgctxt "#30415" +msgid "HD Radio multiplex" +msgstr "" + +msgctxt "#30416" +msgid "DAB ensemble" +msgstr "" + +msgctxt "#30417" +msgid "Weather Radio channel" +msgstr "" + +msgctxt "#30418" +msgid "Select DAB ensemble" +msgstr "" + +# +# 305XX - Setting help text +# + +msgctxt "#30500" +msgid "Specifies the RTL-SDR device connection type. When set to Universal Serial Bus (USB), the device must be connected locally to this system. When set to Network (rtl_tcp), the device must be attached to a system running the rtl_tcp server application." +msgstr "" + +msgctxt "#30501" +msgid "When multiple RTL-SDR devices are connected via Universal Serial Bus (USB), specifies the index of the device to connect to. If only one RTL-SDR device is connected, leave set to the default index 0." +msgstr "" + +msgctxt "#30502" +msgid "Specifies the IPv4 address of the rtl_tcp server where the RTL-SDR device is connected." +msgstr "" + +msgctxt "#30503" +msgid "Specifies the port number that the rtl_tcp server will be listening for client connections. If no port number was specified to rtl_tcp leave set to the default port number 1234." +msgstr "" + +# 30504 can be reused + +msgctxt "#30505" +msgid "Specifies the Digital Signal Processor (DSP) PCM output sample rate." +msgstr "" + +msgctxt "#30506" +msgid "When set to ON detected Radio Data System (RDS) / Radio Broadcast Data System (RBDS) information embedded in the FM signal will be decoded and processed." +msgstr "" + +msgctxt "#30507" +msgid "Specifies the region where the RTL-SDR device is being used. When set to North America, FM Radio will default to the RBDS standard and HD Radio and Weather Radio will be available by default. When set to Europe / Australia, FM Radio will default to the RDS standard and DAB Radio will be enabled by default. When set to World, FM Radio will default to the RDS standard and no other modulations will be enabled by default." +msgstr "" + +msgctxt "#30508" +msgid "Specifies the frequency correction calibration offset to apply to the RTL-SDR device. If the calibration offset for the device is not known, leave set to the default value 0." +msgstr "" + +msgctxt "#30509" +msgid "Specifies the Digital Signal Processor (DSP) PCM output audio gain. Lower gain values will reduce the perceived volume of the audio, whereas higher gain values will increase the perceived volume of the audio." +msgstr "" + +msgctxt "#30510" +msgid "Specifies the input sample rate for the RTL-SDR device. Lower sample rates will improve system performance, whereas higher sample rates will improve audio quality." +msgstr "" + +msgctxt "#30511" +msgid "Specifies the Digital Signal Processor (DSP) downsample quality. When set to Fast, downsampling will be optimized for system performance. When set to Maximum, downsampling will be optimized for audio quality." +msgstr "" + +# 30513 can be reused + +msgctxt "#30514" +msgid "When set to ON new Hybrid Digital (HD) Radio channels may be added and existing HD Radio channels will be available for playback. When set to OFF, HD Radio channels may not be added and existing HD Radio channels will not be available for playback. This option should only be enabled when the RTL-SDR device is in the North America region." +msgstr "" + +msgctxt "#30515" +msgid "When set to ON new Digital Audio Broadcast (DAB) channels may be added and existing DAB channels will be available for playback. When set to OFF, DAB channels may not be added and existing DAB channels will not be available for playback. This option should only be enabled when the RTL-SDR device is in the Europe/Australia region." +msgstr "" + +msgctxt "#30516" +msgid "When set to ON new Weather Radio channels may be added and existing Weather Radio channels will be available for playback. When set to OFF, Weather Radio channels may not be added and existing Weather Radio channels will not be available for playback. This option should only be enabled when the RTL-SDR device is in the North America region." +msgstr "" + +msgctxt "#30517" +msgid "When set to ON the channel number will be prepended to the channel name when reported to Kodi." +msgstr "" diff --git a/pvr.rtlradio/resources/skins/skin.estuary/xml/channelsettings.xml b/pvr.rtlradio/resources/skins/skin.estuary/xml/channelsettings.xml index 0efebdd..e075511 100644 --- a/pvr.rtlradio/resources/skins/skin.estuary/xml/channelsettings.xml +++ b/pvr.rtlradio/resources/skins/skin.estuary/xml/channelsettings.xml @@ -1,215 +1,215 @@ - - - 1000 - Animation_DialogPopupOpenClose - - - 50% - 720 - 50% - 1820 - - - - - - - - - 10 - 70 - 4000 - 4000 - - 0 - 10 - Tuning properties header - 700 - 60 - 26 - font12 - - center - button_focus - black - - - 50 - 205 - 204 - 4000 - 4000 - -25 - - Frequency - 700 - SettingsItemCommon - - false - darkgray - - - Channel modulation - 700 - SettingsItemCommon - - false - darkgray - - - Channel name - 700 - SettingsItemCommon - - darkgray - - - Channel logo button - 700 - SettingsItemCommon - - darkgray - - - - 600 - 301 - Channel logo image - 70 - 48 - keep - - - 0 - 360 - Tuner properties header - 700 - 60 - 26 - font12 - - center - button_focus - black - - - 400 - 202 - 201 - 4000 - 4000 - -25 - - Automatic gain - 700 - SettingsItemCommon - - - - Manual gain - 700 - 200 - SettingsItemCommon - - - - Frequency correction - 700 - 200 - SettingsItemCommon - - - - - - - 700 - 70 - 1000 - 4000 - - 0 - 10 - Signal Meter Header - 700 - 60 - 26 - font12 - - center - button_focus - black - - - 20 - 70 - 692 - 485 - - - -14 - 550 - - - - - Signal Meter gain - 275 - SettingsItemCommon - white - false - - - - 225 - 550 - - - - - Signal Meter power - 300 - SettingsItemCommon - white - false - - - - 485 - 550 - - - - - Signal Meter SNR - 250 - SettingsItemCommon - white - false - - - - - - 10 - 118 - 1000 - 1000 - dialogbuttons_itemgap - 400 - - - - - - - - - - - - - - - - + + + 1000 + Animation_DialogPopupOpenClose + + + 50% + 720 + 50% + 1820 + + + + + + + + + 10 + 70 + 4000 + 4000 + + 0 + 10 + Tuning properties header + 700 + 60 + 26 + font12 + + center + button_focus + black + + + 50 + 205 + 204 + 4000 + 4000 + -25 + + Frequency + 700 + SettingsItemCommon + + false + darkgray + + + Channel modulation + 700 + SettingsItemCommon + + false + darkgray + + + Channel name + 700 + SettingsItemCommon + + darkgray + + + Channel logo button + 700 + SettingsItemCommon + + darkgray + + + + 600 + 301 + Channel logo image + 70 + 48 + keep + + + 0 + 360 + Tuner properties header + 700 + 60 + 26 + font12 + + center + button_focus + black + + + 400 + 202 + 201 + 4000 + 4000 + -25 + + Automatic gain + 700 + SettingsItemCommon + + + + Manual gain + 700 + 200 + SettingsItemCommon + + + + Frequency correction + 700 + 200 + SettingsItemCommon + + + + + + + 700 + 70 + 1000 + 4000 + + 0 + 10 + Signal Meter Header + 700 + 60 + 26 + font12 + + center + button_focus + black + + + 20 + 70 + 692 + 485 + + + -14 + 550 + + + + + Signal Meter gain + 275 + SettingsItemCommon + white + false + + + + 225 + 550 + + + + + Signal Meter power + 300 + SettingsItemCommon + white + false + + + + 485 + 550 + + + + + Signal Meter SNR + 250 + SettingsItemCommon + white + false + + + + + + 10 + 118 + 1000 + 1000 + dialogbuttons_itemgap + 400 + + + + + + + + + + + + + + + + diff --git a/src/align.h b/src/align.h index 4f432f6..240de40 100644 --- a/src/align.h +++ b/src/align.h @@ -1,147 +1,147 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//----------------------------------------------------------------------------- - -#ifndef __ALIGN_H_ -#define __ALIGN_H_ -#pragma once - -#include -#include -#include - -#pragma warning(push, 4) - -// __align -// -// Namespace declarations private to align::up and align::down to prevent name clashes -namespace __align -{ - -// __align::is_signed_integral -// -// Trait to determine if a type is a signed integal -template -struct is_signed_integral - : public std::integral_constant::value && - std::is_signed::value> -{ -}; - -// __align::is_unsigned_integral -// -// Trait to determine if a type is an unsigned integral -template -struct is_unsigned_integral : public std::integral_constant::value && - std::is_unsigned::value> -{ -}; -}; // namespace __align - -// align::up -// align::down -// -// Aligns a pointer or an integral value up or down to an alignment boundary -namespace align -{ - -// align::up (pointers) -// -template -inline typename std::enable_if::value, _type>::type up( - _type value, unsigned int alignment) -{ - if (alignment < 1) - throw std::out_of_range("alignment"); - uintptr_t address = uintptr_t(value); - return reinterpret_cast<_type>( - (address == 0) ? 0 : address + ((alignment - (address % alignment)) % alignment)); -} - -// align::up (unsigned integers) -// -template -inline typename std::enable_if<__align::is_unsigned_integral<_type>::value, _type>::type up( - _type value, unsigned int alignment) -{ - if (alignment < 1) - throw std::out_of_range("alignment"); - return static_cast<_type>((value == 0) ? 0 - : value + ((alignment - (value % alignment)) % alignment)); -} - -// align::up (signed integers) -// -template -inline typename std::enable_if<__align::is_signed_integral<_type>::value, _type>::type up( - _type value, int alignment) -{ - if (alignment < 1) - throw std::out_of_range("alignment"); - return static_cast<_type>((value == 0) ? 0 - : value + ((alignment - (value % alignment)) % alignment)); -} - -// align::down (pointers) -// -template -inline typename std::enable_if::value, _type>::type down( - _type value, unsigned int alignment) -{ - if (alignment < 1) - throw std::out_of_range("alignment"); - uintptr_t address = uintptr_t(value); - return reinterpret_cast<_type>( - (address < alignment) ? 0 : up(address - (alignment - 1), alignment)); -} - -// align::down (unsigned integers) -// -template -inline typename std::enable_if<__align::is_unsigned_integral<_type>::value, _type>::type down( - _type value, unsigned int alignment) -{ - if (alignment < 1) - throw std::out_of_range("alignment"); - return static_cast<_type>((value < alignment) ? 0 - : up<_type>(value - (alignment - 1), alignment)); -} - -// align::down (signed integers) -// -template -inline typename std::enable_if<__align::is_signed_integral<_type>::value, _type>::type down( - _type value, int alignment) -{ - if (alignment < 1) - throw std::out_of_range("alignment"); - return static_cast<_type>((value < alignment) ? 0 - : up<_type>(value - (alignment - 1), alignment)); -} -}; // namespace align - -//----------------------------------------------------------------------------- - -#pragma warning(pop) - -#endif // __ALIGN_H_ +//----------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef __ALIGN_H_ +#define __ALIGN_H_ +#pragma once + +#include +#include +#include + +#pragma warning(push, 4) + +// __align +// +// Namespace declarations private to align::up and align::down to prevent name clashes +namespace __align +{ + +// __align::is_signed_integral +// +// Trait to determine if a type is a signed integal +template +struct is_signed_integral + : public std::integral_constant::value && + std::is_signed::value> +{ +}; + +// __align::is_unsigned_integral +// +// Trait to determine if a type is an unsigned integral +template +struct is_unsigned_integral : public std::integral_constant::value && + std::is_unsigned::value> +{ +}; +}; // namespace __align + +// align::up +// align::down +// +// Aligns a pointer or an integral value up or down to an alignment boundary +namespace align +{ + +// align::up (pointers) +// +template +inline typename std::enable_if::value, _type>::type up( + _type value, unsigned int alignment) +{ + if (alignment < 1) + throw std::out_of_range("alignment"); + uintptr_t address = uintptr_t(value); + return reinterpret_cast<_type>( + (address == 0) ? 0 : address + ((alignment - (address % alignment)) % alignment)); +} + +// align::up (unsigned integers) +// +template +inline typename std::enable_if<__align::is_unsigned_integral<_type>::value, _type>::type up( + _type value, unsigned int alignment) +{ + if (alignment < 1) + throw std::out_of_range("alignment"); + return static_cast<_type>((value == 0) ? 0 + : value + ((alignment - (value % alignment)) % alignment)); +} + +// align::up (signed integers) +// +template +inline typename std::enable_if<__align::is_signed_integral<_type>::value, _type>::type up( + _type value, int alignment) +{ + if (alignment < 1) + throw std::out_of_range("alignment"); + return static_cast<_type>((value == 0) ? 0 + : value + ((alignment - (value % alignment)) % alignment)); +} + +// align::down (pointers) +// +template +inline typename std::enable_if::value, _type>::type down( + _type value, unsigned int alignment) +{ + if (alignment < 1) + throw std::out_of_range("alignment"); + uintptr_t address = uintptr_t(value); + return reinterpret_cast<_type>( + (address < alignment) ? 0 : up(address - (alignment - 1), alignment)); +} + +// align::down (unsigned integers) +// +template +inline typename std::enable_if<__align::is_unsigned_integral<_type>::value, _type>::type down( + _type value, unsigned int alignment) +{ + if (alignment < 1) + throw std::out_of_range("alignment"); + return static_cast<_type>((value < alignment) ? 0 + : up<_type>(value - (alignment - 1), alignment)); +} + +// align::down (signed integers) +// +template +inline typename std::enable_if<__align::is_signed_integral<_type>::value, _type>::type down( + _type value, int alignment) +{ + if (alignment < 1) + throw std::out_of_range("alignment"); + return static_cast<_type>((value < alignment) ? 0 + : up<_type>(value - (alignment - 1), alignment)); +} +}; // namespace align + +//----------------------------------------------------------------------------- + +#pragma warning(pop) + +#endif // __ALIGN_H_ diff --git a/src/anglepch.cpp b/src/anglepch.cpp index 03b8cc2..c140010 100644 --- a/src/anglepch.cpp +++ b/src/anglepch.cpp @@ -1,25 +1,25 @@ -//--------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//--------------------------------------------------------------------------- - -#include "anglepch.h" - -//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//--------------------------------------------------------------------------- + +#include "anglepch.h" + +//--------------------------------------------------------------------------- diff --git a/src/anglepch.h b/src/anglepch.h index 50cdb53..e56e80e 100644 --- a/src/anglepch.h +++ b/src/anglepch.h @@ -1,324 +1,324 @@ -//--------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//--------------------------------------------------------------------------- - - -#ifndef __ANGLEPCH_H_ -#define __ANGLEPCH_H_ -#pragma once - -#include -/* -#ifndef D3DERR_OUTOFVIDEOMEMORY -#define D3DERR_OUTOFVIDEOMEMORY E_FAIL -#endif - -// ANGLE takes a terribly long time to build; throwing as many headers as possible -// into a forced precompiled header makes a tremendous difference ... - -#include "common/Color.h" -#include "common/FastVector.h" -#include "common/FixedVector.h" -#include "common/MemoryBuffer.h" -#include "common/Optional.h" -#include "common/PackedEGLEnums_autogen.h" -#include "common/PackedEnums.h" -#include "common/PackedGLEnums_autogen.h" -#include "common/PoolAlloc.h" -#include "common/aligned_memory.h" -#include "common/android_util.h" -#include "common/angleutils.h" -#include "common/bitset_utils.h" -#include "common/debug.h" -#include "common/event_tracer.h" -#include "common/hash_utils.h" -#include "common/mathutil.h" -#include "common/matrix_utils.h" -#include "common/platform.h" -#include "common/string_utils.h" -#include "common/system_utils.h" -#include "common/tls.h" -#include "common/utilities.h" -#include "common/vector_utils.h" -#include "common/version.h" -#include "compiler/preprocessor/DiagnosticsBase.h" -#include "compiler/preprocessor/DirectiveHandlerBase.h" -#include "compiler/preprocessor/DirectiveParser.h" -#include "compiler/preprocessor/ExpressionParser.h" -#include "compiler/preprocessor/Input.h" -#include "compiler/preprocessor/Lexer.h" -#include "compiler/preprocessor/Macro.h" -#include "compiler/preprocessor/MacroExpander.h" -#include "compiler/preprocessor/Preprocessor.h" -#include "compiler/preprocessor/SourceLocation.h" -#include "compiler/preprocessor/Token.h" -#include "compiler/preprocessor/Tokenizer.h" -#include "compiler/preprocessor/numeric_lex.h" -#include "compiler/translator/ASTMetadataHLSL.h" -#include "compiler/translator/AtomicCounterFunctionHLSL.h" -#include "compiler/translator/BaseTypes.h" -#include "compiler/translator/BuiltInFunctionEmulator.h" -#include "compiler/translator/BuiltInFunctionEmulatorGLSL.h" -#include "compiler/translator/BuiltInFunctionEmulatorHLSL.h" -#include "compiler/translator/CallDAG.h" -#include "compiler/translator/CollectVariables.h" -#include "compiler/translator/Common.h" -#include "compiler/translator/Compiler.h" -#include "compiler/translator/ConstantUnion.h" -#include "compiler/translator/Declarator.h" -#include "compiler/translator/Diagnostics.h" -#include "compiler/translator/DirectiveHandler.h" -#include "compiler/translator/ExtensionBehavior.h" -#include "compiler/translator/ExtensionGLSL.h" -#include "compiler/translator/FlagStd140Structs.h" -#include "compiler/translator/FunctionLookup.h" -#include "compiler/translator/HashNames.h" -#include "compiler/translator/ImageFunctionHLSL.h" -#include "compiler/translator/ImmutableString.h" -#include "compiler/translator/ImmutableStringBuilder.h" -#include "compiler/translator/InfoSink.h" -#include "compiler/translator/Initialize.h" -#include "compiler/translator/InitializeDll.h" -#include "compiler/translator/InitializeGlobals.h" -#include "compiler/translator/IntermNode.h" -#include "compiler/translator/IsASTDepthBelowLimit.h" -#include "compiler/translator/Operator.h" -#include "compiler/translator/OutputESSL.h" -#include "compiler/translator/OutputGLSL.h" -#include "compiler/translator/OutputGLSLBase.h" -#include "compiler/translator/OutputHLSL.h" -#include "compiler/translator/OutputTree.h" -#include "compiler/translator/ParseContext.h" -#include "compiler/translator/blocklayout.h" -#include "compiler/translator/blocklayoutHLSL.h" -#include "compiler/translator/glslang.h" -#include "compiler/translator/length_limits.h" -//#include "compiler/translator/ParseContext_autogen.h" -#include "compiler/translator/PoolAlloc.h" -#include "compiler/translator/Pragma.h" -#include "compiler/translator/QualifierTypes.h" -#include "compiler/translator/ResourcesHLSL.h" -#include "compiler/translator/Severity.h" -#include "compiler/translator/ShaderStorageBlockFunctionHLSL.h" -#include "compiler/translator/ShaderStorageBlockOutputHLSL.h" -#include "compiler/translator/StaticType.h" -#include "compiler/translator/StructureHLSL.h" -#include "compiler/translator/Symbol.h" -#include "compiler/translator/SymbolTable.h" -#include "compiler/translator/SymbolTable_autogen.h" -#include "compiler/translator/SymbolUniqueId.h" -#include "compiler/translator/TextureFunctionHLSL.h" -#include "compiler/translator/TranslatorESSL.h" -#include "compiler/translator/TranslatorGLSL.h" -#include "compiler/translator/TranslatorHLSL.h" -#include "compiler/translator/Types.h" -//#include "compiler/translator/util.h" -#include "compiler/translator/UtilsHLSL.h" -#include "compiler/translator/ValidateAST.h" -#include "compiler/translator/ValidateGlobalInitializer.h" -#include "compiler/translator/ValidateLimitations.h" -#include "compiler/translator/ValidateMaxParameters.h" -#include "compiler/translator/ValidateOutputs.h" -#include "compiler/translator/ValidateSwitch.h" -#include "compiler/translator/ValidateVaryingLocations.h" -#include "compiler/translator/VariablePacker.h" -#include "compiler/translator/VersionGLSL.h" - -// Skip compiler/translator/tree_ops/*.h; causes problems -// -// Skip compiler/translator/tree_util/*.h; causes problems - -#include "libANGLE/AttributeMap.h" -#include "libANGLE/BinaryStream.h" -#include "libANGLE/BlobCache.h" -#include "libANGLE/Buffer.h" -#include "libANGLE/Caps.h" -#include "libANGLE/Compiler.h" -#include "libANGLE/Config.h" -#include "libANGLE/Constants.h" -#include "libANGLE/Context.h" -#include "libANGLE/Context.inl.h" -#include "libANGLE/Context_gles_1_0_autogen.h" -#include "libANGLE/Debug.h" -#include "libANGLE/Device.h" -#include "libANGLE/Display.h" -#include "libANGLE/EGLSync.h" -#include "libANGLE/Error.h" -#include "libANGLE/ErrorStrings.h" -#include "libANGLE/Fence.h" -#include "libANGLE/Framebuffer.h" -#include "libANGLE/FramebufferAttachment.h" -#include "libANGLE/GLES1Renderer.h" -#include "libANGLE/GLES1State.h" -#include "libANGLE/HandleAllocator.h" -#include "libANGLE/HandleRangeAllocator.h" -#include "libANGLE/Image.h" -#include "libANGLE/ImageIndex.h" -#include "libANGLE/IndexRangeCache.h" -#include "libANGLE/LoggingAnnotator.h" -#include "libANGLE/MemoryObject.h" -#include "libANGLE/MemoryProgramCache.h" -#include "libANGLE/Observer.h" -#include "libANGLE/Overlay.h" -#include "libANGLE/OverlayWidgets.h" -#include "libANGLE/Program.h" -#include "libANGLE/ProgramLinkedResources.h" -#include "libANGLE/ProgramPipeline.h" -#include "libANGLE/Query.h" -#include "libANGLE/RefCountObject.h" -#include "libANGLE/Renderbuffer.h" -#include "libANGLE/ResourceManager.h" -#include "libANGLE/ResourceMap.h" -#include "libANGLE/Sampler.h" -#include "libANGLE/Semaphore.h" -#include "libANGLE/Shader.h" -#include "libANGLE/SizedMRUCache.h" -#include "libANGLE/State.h" -#include "libANGLE/Stream.h" -#include "libANGLE/Surface.h" -#include "libANGLE/Texture.h" -#include "libANGLE/Thread.h" -#include "libANGLE/TransformFeedback.h" -#include "libANGLE/Uniform.h" -#include "libANGLE/VaryingPacking.h" -#include "libANGLE/Version.h" -#include "libANGLE/VertexArray.h" -#include "libANGLE/VertexAttribute.h" -#include "libANGLE/WorkerThread.h" -#include "libANGLE/angletypes.h" -#include "libANGLE/features.h" -#include "libANGLE/formatutils.h" -#include "libANGLE/histogram_macros.h" -#include "libANGLE/queryconversions.h" -#include "libANGLE/queryutils.h" -#include "libANGLE/renderer/BufferImpl.h" -#include "libANGLE/renderer/CompilerImpl.h" -#include "libANGLE/renderer/ContextImpl.h" -#include "libANGLE/renderer/DeviceImpl.h" -#include "libANGLE/renderer/DisplayImpl.h" -#include "libANGLE/renderer/EGLImplFactory.h" -#include "libANGLE/renderer/EGLSyncImpl.h" -#include "libANGLE/renderer/FenceNVImpl.h" -#include "libANGLE/renderer/Format.h" -#include "libANGLE/renderer/FormatID_autogen.h" -#include "libANGLE/renderer/FramebufferAttachmentObjectImpl.h" -#include "libANGLE/renderer/FramebufferImpl.h" -#include "libANGLE/renderer/GLImplFactory.h" -#include "libANGLE/renderer/ImageImpl.h" -#include "libANGLE/renderer/PathImpl.h" -#include "libANGLE/renderer/ProgramImpl.h" -#include "libANGLE/renderer/ProgramPipelineImpl.h" -#include "libANGLE/renderer/QueryImpl.h" -#include "libANGLE/renderer/RenderTargetCache.h" -#include "libANGLE/renderer/RenderbufferImpl.h" -#include "libANGLE/renderer/SamplerImpl.h" -#include "libANGLE/renderer/SemaphoreImpl.h" -#include "libANGLE/renderer/ShaderImpl.h" -#include "libANGLE/renderer/StreamProducerImpl.h" -#include "libANGLE/renderer/SurfaceImpl.h" -#include "libANGLE/renderer/SyncImpl.h" -#include "libANGLE/renderer/TextureImpl.h" -#include "libANGLE/renderer/TransformFeedbackImpl.h" -#include "libANGLE/renderer/VertexArrayImpl.h" -#include "libANGLE/renderer/copyvertex.h" -#include "libANGLE/renderer/d3d/BufferD3D.h" -#include "libANGLE/renderer/d3d/CompilerD3D.h" -#include "libANGLE/renderer/d3d/ContextD3D.h" -#include "libANGLE/renderer/d3d/DeviceD3D.h" -#include "libANGLE/renderer/d3d/DisplayD3D.h" -#include "libANGLE/renderer/d3d/DynamicHLSL.h" -#include "libANGLE/renderer/d3d/DynamicImage2DHLSL.h" -#include "libANGLE/renderer/d3d/EGLImageD3D.h" -#include "libANGLE/renderer/d3d/FramebufferD3D.h" -#include "libANGLE/renderer/d3d/HLSLCompiler.h" -#include "libANGLE/renderer/d3d/ImageD3D.h" -#include "libANGLE/renderer/d3d/IndexBuffer.h" -#include "libANGLE/renderer/d3d/IndexDataManager.h" -#include "libANGLE/renderer/d3d/NativeWindowD3D.h" -#include "libANGLE/renderer/d3d/ProgramD3D.h" -#include "libANGLE/renderer/d3d/RenderTargetD3D.h" -#include "libANGLE/renderer/d3d/RenderbufferD3D.h" -#include "libANGLE/renderer/d3d/RendererD3D.h" -#include "libANGLE/renderer/d3d/SamplerD3D.h" -#include "libANGLE/renderer/d3d/ShaderD3D.h" -#include "libANGLE/renderer/d3d/ShaderExecutableD3D.h" -#include "libANGLE/renderer/d3d/SurfaceD3D.h" -#include "libANGLE/renderer/d3d/SwapChainD3D.h" -#include "libANGLE/renderer/d3d/TextureD3D.h" -#include "libANGLE/renderer/d3d/TextureStorage.h" -#include "libANGLE/renderer/d3d/VertexBuffer.h" -#include "libANGLE/renderer/d3d/VertexDataManager.h" -#include "libANGLE/renderer/d3d/d3d11/Blit11.h" -#include "libANGLE/renderer/d3d/d3d11/Buffer11.h" -#include "libANGLE/renderer/d3d/d3d11/Clear11.h" -#include "libANGLE/renderer/d3d/d3d11/Context11.h" -#include "libANGLE/renderer/d3d/d3d11/DebugAnnotator11.h" -#include "libANGLE/renderer/d3d/d3d11/ExternalImageSiblingImpl11.h" -#include "libANGLE/renderer/d3d/d3d11/Fence11.h" -#include "libANGLE/renderer/d3d/d3d11/Framebuffer11.h" -#include "libANGLE/renderer/d3d/d3d11/Image11.h" -#include "libANGLE/renderer/d3d/d3d11/IndexBuffer11.h" -#include "libANGLE/renderer/d3d/d3d11/InputLayoutCache.h" -#include "libANGLE/renderer/d3d/d3d11/MappedSubresourceVerifier11.h" -#include "libANGLE/renderer/d3d/d3d11/NativeWindow11.h" -#include "libANGLE/renderer/d3d/d3d11/PixelTransfer11.h" -#include "libANGLE/renderer/d3d/d3d11/Program11.h" -#include "libANGLE/renderer/d3d/d3d11/ProgramPipeline11.h" -#include "libANGLE/renderer/d3d/d3d11/Query11.h" -#include "libANGLE/renderer/d3d/d3d11/RenderStateCache.h" -#include "libANGLE/renderer/d3d/d3d11/RenderTarget11.h" -#include "libANGLE/renderer/d3d/d3d11/Renderer11.h" -#include "libANGLE/renderer/d3d/d3d11/ResourceManager11.h" -#include "libANGLE/renderer/d3d/d3d11/ShaderExecutable11.h" -#include "libANGLE/renderer/d3d/d3d11/StateManager11.h" -#include "libANGLE/renderer/d3d/d3d11/StreamProducerD3DTexture.h" -#include "libANGLE/renderer/d3d/d3d11/SwapChain11.h" -#include "libANGLE/renderer/d3d/d3d11/TextureStorage11.h" -#include "libANGLE/renderer/d3d/d3d11/TransformFeedback11.h" -#include "libANGLE/renderer/d3d/d3d11/Trim11.h" -#include "libANGLE/renderer/d3d/d3d11/VertexArray11.h" -#include "libANGLE/renderer/d3d/d3d11/VertexBuffer11.h" -#include "libANGLE/renderer/d3d/d3d11/dxgi_support_table.h" -#include "libANGLE/renderer/d3d/d3d11/formatutils11.h" -#include "libANGLE/renderer/d3d/d3d11/renderer11_utils.h" -#include "libANGLE/renderer/d3d/d3d11/texture_format_table.h" -#include "libANGLE/renderer/d3d/d3d11/texture_format_table_utils.h" -#include "libANGLE/renderer/d3d/formatutilsD3D.h" -#include "libANGLE/renderer/driver_utils.h" -#include "libANGLE/renderer/load_functions_table.h" -#include "libANGLE/renderer/renderer_utils.h" -#include "libANGLE/validationEGL.h" -#include "libANGLE/validationES.h" -#include "libANGLE/validationES1.h" -#include "libANGLE/validationES1_autogen.h" -#include "libANGLE/validationES2.h" -#include "libANGLE/validationES2_autogen.h" -#include "libANGLE/validationES3.h" -#include "libANGLE/validationES31.h" -#include "libANGLE/validationES31_autogen.h" -#include "libANGLE/validationES3_autogen.h" -#include "libANGLE/validationESEXT.h" -#include "libANGLE/validationESEXT_autogen.h" - -*/ -//--------------------------------------------------------------------------- - -#endif // __ANGLEPCH_H_ +//--------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//--------------------------------------------------------------------------- + + +#ifndef __ANGLEPCH_H_ +#define __ANGLEPCH_H_ +#pragma once + +#include +/* +#ifndef D3DERR_OUTOFVIDEOMEMORY +#define D3DERR_OUTOFVIDEOMEMORY E_FAIL +#endif + +// ANGLE takes a terribly long time to build; throwing as many headers as possible +// into a forced precompiled header makes a tremendous difference ... + +#include "common/Color.h" +#include "common/FastVector.h" +#include "common/FixedVector.h" +#include "common/MemoryBuffer.h" +#include "common/Optional.h" +#include "common/PackedEGLEnums_autogen.h" +#include "common/PackedEnums.h" +#include "common/PackedGLEnums_autogen.h" +#include "common/PoolAlloc.h" +#include "common/aligned_memory.h" +#include "common/android_util.h" +#include "common/angleutils.h" +#include "common/bitset_utils.h" +#include "common/debug.h" +#include "common/event_tracer.h" +#include "common/hash_utils.h" +#include "common/mathutil.h" +#include "common/matrix_utils.h" +#include "common/platform.h" +#include "common/string_utils.h" +#include "common/system_utils.h" +#include "common/tls.h" +#include "common/utilities.h" +#include "common/vector_utils.h" +#include "common/version.h" +#include "compiler/preprocessor/DiagnosticsBase.h" +#include "compiler/preprocessor/DirectiveHandlerBase.h" +#include "compiler/preprocessor/DirectiveParser.h" +#include "compiler/preprocessor/ExpressionParser.h" +#include "compiler/preprocessor/Input.h" +#include "compiler/preprocessor/Lexer.h" +#include "compiler/preprocessor/Macro.h" +#include "compiler/preprocessor/MacroExpander.h" +#include "compiler/preprocessor/Preprocessor.h" +#include "compiler/preprocessor/SourceLocation.h" +#include "compiler/preprocessor/Token.h" +#include "compiler/preprocessor/Tokenizer.h" +#include "compiler/preprocessor/numeric_lex.h" +#include "compiler/translator/ASTMetadataHLSL.h" +#include "compiler/translator/AtomicCounterFunctionHLSL.h" +#include "compiler/translator/BaseTypes.h" +#include "compiler/translator/BuiltInFunctionEmulator.h" +#include "compiler/translator/BuiltInFunctionEmulatorGLSL.h" +#include "compiler/translator/BuiltInFunctionEmulatorHLSL.h" +#include "compiler/translator/CallDAG.h" +#include "compiler/translator/CollectVariables.h" +#include "compiler/translator/Common.h" +#include "compiler/translator/Compiler.h" +#include "compiler/translator/ConstantUnion.h" +#include "compiler/translator/Declarator.h" +#include "compiler/translator/Diagnostics.h" +#include "compiler/translator/DirectiveHandler.h" +#include "compiler/translator/ExtensionBehavior.h" +#include "compiler/translator/ExtensionGLSL.h" +#include "compiler/translator/FlagStd140Structs.h" +#include "compiler/translator/FunctionLookup.h" +#include "compiler/translator/HashNames.h" +#include "compiler/translator/ImageFunctionHLSL.h" +#include "compiler/translator/ImmutableString.h" +#include "compiler/translator/ImmutableStringBuilder.h" +#include "compiler/translator/InfoSink.h" +#include "compiler/translator/Initialize.h" +#include "compiler/translator/InitializeDll.h" +#include "compiler/translator/InitializeGlobals.h" +#include "compiler/translator/IntermNode.h" +#include "compiler/translator/IsASTDepthBelowLimit.h" +#include "compiler/translator/Operator.h" +#include "compiler/translator/OutputESSL.h" +#include "compiler/translator/OutputGLSL.h" +#include "compiler/translator/OutputGLSLBase.h" +#include "compiler/translator/OutputHLSL.h" +#include "compiler/translator/OutputTree.h" +#include "compiler/translator/ParseContext.h" +#include "compiler/translator/blocklayout.h" +#include "compiler/translator/blocklayoutHLSL.h" +#include "compiler/translator/glslang.h" +#include "compiler/translator/length_limits.h" +//#include "compiler/translator/ParseContext_autogen.h" +#include "compiler/translator/PoolAlloc.h" +#include "compiler/translator/Pragma.h" +#include "compiler/translator/QualifierTypes.h" +#include "compiler/translator/ResourcesHLSL.h" +#include "compiler/translator/Severity.h" +#include "compiler/translator/ShaderStorageBlockFunctionHLSL.h" +#include "compiler/translator/ShaderStorageBlockOutputHLSL.h" +#include "compiler/translator/StaticType.h" +#include "compiler/translator/StructureHLSL.h" +#include "compiler/translator/Symbol.h" +#include "compiler/translator/SymbolTable.h" +#include "compiler/translator/SymbolTable_autogen.h" +#include "compiler/translator/SymbolUniqueId.h" +#include "compiler/translator/TextureFunctionHLSL.h" +#include "compiler/translator/TranslatorESSL.h" +#include "compiler/translator/TranslatorGLSL.h" +#include "compiler/translator/TranslatorHLSL.h" +#include "compiler/translator/Types.h" +//#include "compiler/translator/util.h" +#include "compiler/translator/UtilsHLSL.h" +#include "compiler/translator/ValidateAST.h" +#include "compiler/translator/ValidateGlobalInitializer.h" +#include "compiler/translator/ValidateLimitations.h" +#include "compiler/translator/ValidateMaxParameters.h" +#include "compiler/translator/ValidateOutputs.h" +#include "compiler/translator/ValidateSwitch.h" +#include "compiler/translator/ValidateVaryingLocations.h" +#include "compiler/translator/VariablePacker.h" +#include "compiler/translator/VersionGLSL.h" + +// Skip compiler/translator/tree_ops/*.h; causes problems +// +// Skip compiler/translator/tree_util/*.h; causes problems + +#include "libANGLE/AttributeMap.h" +#include "libANGLE/BinaryStream.h" +#include "libANGLE/BlobCache.h" +#include "libANGLE/Buffer.h" +#include "libANGLE/Caps.h" +#include "libANGLE/Compiler.h" +#include "libANGLE/Config.h" +#include "libANGLE/Constants.h" +#include "libANGLE/Context.h" +#include "libANGLE/Context.inl.h" +#include "libANGLE/Context_gles_1_0_autogen.h" +#include "libANGLE/Debug.h" +#include "libANGLE/Device.h" +#include "libANGLE/Display.h" +#include "libANGLE/EGLSync.h" +#include "libANGLE/Error.h" +#include "libANGLE/ErrorStrings.h" +#include "libANGLE/Fence.h" +#include "libANGLE/Framebuffer.h" +#include "libANGLE/FramebufferAttachment.h" +#include "libANGLE/GLES1Renderer.h" +#include "libANGLE/GLES1State.h" +#include "libANGLE/HandleAllocator.h" +#include "libANGLE/HandleRangeAllocator.h" +#include "libANGLE/Image.h" +#include "libANGLE/ImageIndex.h" +#include "libANGLE/IndexRangeCache.h" +#include "libANGLE/LoggingAnnotator.h" +#include "libANGLE/MemoryObject.h" +#include "libANGLE/MemoryProgramCache.h" +#include "libANGLE/Observer.h" +#include "libANGLE/Overlay.h" +#include "libANGLE/OverlayWidgets.h" +#include "libANGLE/Program.h" +#include "libANGLE/ProgramLinkedResources.h" +#include "libANGLE/ProgramPipeline.h" +#include "libANGLE/Query.h" +#include "libANGLE/RefCountObject.h" +#include "libANGLE/Renderbuffer.h" +#include "libANGLE/ResourceManager.h" +#include "libANGLE/ResourceMap.h" +#include "libANGLE/Sampler.h" +#include "libANGLE/Semaphore.h" +#include "libANGLE/Shader.h" +#include "libANGLE/SizedMRUCache.h" +#include "libANGLE/State.h" +#include "libANGLE/Stream.h" +#include "libANGLE/Surface.h" +#include "libANGLE/Texture.h" +#include "libANGLE/Thread.h" +#include "libANGLE/TransformFeedback.h" +#include "libANGLE/Uniform.h" +#include "libANGLE/VaryingPacking.h" +#include "libANGLE/Version.h" +#include "libANGLE/VertexArray.h" +#include "libANGLE/VertexAttribute.h" +#include "libANGLE/WorkerThread.h" +#include "libANGLE/angletypes.h" +#include "libANGLE/features.h" +#include "libANGLE/formatutils.h" +#include "libANGLE/histogram_macros.h" +#include "libANGLE/queryconversions.h" +#include "libANGLE/queryutils.h" +#include "libANGLE/renderer/BufferImpl.h" +#include "libANGLE/renderer/CompilerImpl.h" +#include "libANGLE/renderer/ContextImpl.h" +#include "libANGLE/renderer/DeviceImpl.h" +#include "libANGLE/renderer/DisplayImpl.h" +#include "libANGLE/renderer/EGLImplFactory.h" +#include "libANGLE/renderer/EGLSyncImpl.h" +#include "libANGLE/renderer/FenceNVImpl.h" +#include "libANGLE/renderer/Format.h" +#include "libANGLE/renderer/FormatID_autogen.h" +#include "libANGLE/renderer/FramebufferAttachmentObjectImpl.h" +#include "libANGLE/renderer/FramebufferImpl.h" +#include "libANGLE/renderer/GLImplFactory.h" +#include "libANGLE/renderer/ImageImpl.h" +#include "libANGLE/renderer/PathImpl.h" +#include "libANGLE/renderer/ProgramImpl.h" +#include "libANGLE/renderer/ProgramPipelineImpl.h" +#include "libANGLE/renderer/QueryImpl.h" +#include "libANGLE/renderer/RenderTargetCache.h" +#include "libANGLE/renderer/RenderbufferImpl.h" +#include "libANGLE/renderer/SamplerImpl.h" +#include "libANGLE/renderer/SemaphoreImpl.h" +#include "libANGLE/renderer/ShaderImpl.h" +#include "libANGLE/renderer/StreamProducerImpl.h" +#include "libANGLE/renderer/SurfaceImpl.h" +#include "libANGLE/renderer/SyncImpl.h" +#include "libANGLE/renderer/TextureImpl.h" +#include "libANGLE/renderer/TransformFeedbackImpl.h" +#include "libANGLE/renderer/VertexArrayImpl.h" +#include "libANGLE/renderer/copyvertex.h" +#include "libANGLE/renderer/d3d/BufferD3D.h" +#include "libANGLE/renderer/d3d/CompilerD3D.h" +#include "libANGLE/renderer/d3d/ContextD3D.h" +#include "libANGLE/renderer/d3d/DeviceD3D.h" +#include "libANGLE/renderer/d3d/DisplayD3D.h" +#include "libANGLE/renderer/d3d/DynamicHLSL.h" +#include "libANGLE/renderer/d3d/DynamicImage2DHLSL.h" +#include "libANGLE/renderer/d3d/EGLImageD3D.h" +#include "libANGLE/renderer/d3d/FramebufferD3D.h" +#include "libANGLE/renderer/d3d/HLSLCompiler.h" +#include "libANGLE/renderer/d3d/ImageD3D.h" +#include "libANGLE/renderer/d3d/IndexBuffer.h" +#include "libANGLE/renderer/d3d/IndexDataManager.h" +#include "libANGLE/renderer/d3d/NativeWindowD3D.h" +#include "libANGLE/renderer/d3d/ProgramD3D.h" +#include "libANGLE/renderer/d3d/RenderTargetD3D.h" +#include "libANGLE/renderer/d3d/RenderbufferD3D.h" +#include "libANGLE/renderer/d3d/RendererD3D.h" +#include "libANGLE/renderer/d3d/SamplerD3D.h" +#include "libANGLE/renderer/d3d/ShaderD3D.h" +#include "libANGLE/renderer/d3d/ShaderExecutableD3D.h" +#include "libANGLE/renderer/d3d/SurfaceD3D.h" +#include "libANGLE/renderer/d3d/SwapChainD3D.h" +#include "libANGLE/renderer/d3d/TextureD3D.h" +#include "libANGLE/renderer/d3d/TextureStorage.h" +#include "libANGLE/renderer/d3d/VertexBuffer.h" +#include "libANGLE/renderer/d3d/VertexDataManager.h" +#include "libANGLE/renderer/d3d/d3d11/Blit11.h" +#include "libANGLE/renderer/d3d/d3d11/Buffer11.h" +#include "libANGLE/renderer/d3d/d3d11/Clear11.h" +#include "libANGLE/renderer/d3d/d3d11/Context11.h" +#include "libANGLE/renderer/d3d/d3d11/DebugAnnotator11.h" +#include "libANGLE/renderer/d3d/d3d11/ExternalImageSiblingImpl11.h" +#include "libANGLE/renderer/d3d/d3d11/Fence11.h" +#include "libANGLE/renderer/d3d/d3d11/Framebuffer11.h" +#include "libANGLE/renderer/d3d/d3d11/Image11.h" +#include "libANGLE/renderer/d3d/d3d11/IndexBuffer11.h" +#include "libANGLE/renderer/d3d/d3d11/InputLayoutCache.h" +#include "libANGLE/renderer/d3d/d3d11/MappedSubresourceVerifier11.h" +#include "libANGLE/renderer/d3d/d3d11/NativeWindow11.h" +#include "libANGLE/renderer/d3d/d3d11/PixelTransfer11.h" +#include "libANGLE/renderer/d3d/d3d11/Program11.h" +#include "libANGLE/renderer/d3d/d3d11/ProgramPipeline11.h" +#include "libANGLE/renderer/d3d/d3d11/Query11.h" +#include "libANGLE/renderer/d3d/d3d11/RenderStateCache.h" +#include "libANGLE/renderer/d3d/d3d11/RenderTarget11.h" +#include "libANGLE/renderer/d3d/d3d11/Renderer11.h" +#include "libANGLE/renderer/d3d/d3d11/ResourceManager11.h" +#include "libANGLE/renderer/d3d/d3d11/ShaderExecutable11.h" +#include "libANGLE/renderer/d3d/d3d11/StateManager11.h" +#include "libANGLE/renderer/d3d/d3d11/StreamProducerD3DTexture.h" +#include "libANGLE/renderer/d3d/d3d11/SwapChain11.h" +#include "libANGLE/renderer/d3d/d3d11/TextureStorage11.h" +#include "libANGLE/renderer/d3d/d3d11/TransformFeedback11.h" +#include "libANGLE/renderer/d3d/d3d11/Trim11.h" +#include "libANGLE/renderer/d3d/d3d11/VertexArray11.h" +#include "libANGLE/renderer/d3d/d3d11/VertexBuffer11.h" +#include "libANGLE/renderer/d3d/d3d11/dxgi_support_table.h" +#include "libANGLE/renderer/d3d/d3d11/formatutils11.h" +#include "libANGLE/renderer/d3d/d3d11/renderer11_utils.h" +#include "libANGLE/renderer/d3d/d3d11/texture_format_table.h" +#include "libANGLE/renderer/d3d/d3d11/texture_format_table_utils.h" +#include "libANGLE/renderer/d3d/formatutilsD3D.h" +#include "libANGLE/renderer/driver_utils.h" +#include "libANGLE/renderer/load_functions_table.h" +#include "libANGLE/renderer/renderer_utils.h" +#include "libANGLE/validationEGL.h" +#include "libANGLE/validationES.h" +#include "libANGLE/validationES1.h" +#include "libANGLE/validationES1_autogen.h" +#include "libANGLE/validationES2.h" +#include "libANGLE/validationES2_autogen.h" +#include "libANGLE/validationES3.h" +#include "libANGLE/validationES31.h" +#include "libANGLE/validationES31_autogen.h" +#include "libANGLE/validationES3_autogen.h" +#include "libANGLE/validationESEXT.h" +#include "libANGLE/validationESEXT_autogen.h" + +*/ +//--------------------------------------------------------------------------- + +#endif // __ANGLEPCH_H_ diff --git a/src/channelsettings.cpp b/src/channelsettings.cpp index 12e9ab2..94ce688 100644 --- a/src/channelsettings.cpp +++ b/src/channelsettings.cpp @@ -1,1364 +1,1364 @@ -//--------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//--------------------------------------------------------------------------- - -#include "channelsettings.h" - -#include "dabmuxscanner.h" -#include "hdmuxscanner.h" -#include "stdafx.h" -#include "string_exception.h" - -#include -#include -#include -#include -#include -#include - -#ifdef HAS_GLES -#include -#endif - -#pragma warning(push, 4) - -// Control Identifiers -// -static const int CONTROL_LABEL_HEADERLABEL = 2; -static const int CONTROL_BUTTON_OK = 100; -static const int CONTROL_BUTTON_CANCEL = 101; -static const int CONTROL_EDIT_FREQUENCY = 200; -static const int CONTROL_EDIT_CHANNELNAME = 201; -static const int CONTROL_BUTTON_CHANNELICON = 202; -static const int CONTROL_IMAGE_CHANNELICON = 203; -static const int CONTROL_RADIO_AUTOMATICGAIN = 204; -static const int CONTROL_SLIDER_MANUALGAIN = 205; -static const int CONTROL_RENDER_SIGNALMETER = 206; -static const int CONTROL_EDIT_METERGAIN = 207; -static const int CONTROL_EDIT_METERPOWER = 208; -static const int CONTROL_EDIT_METERSNR = 209; -static const int CONTROL_EDIT_MODULATION = 210; -static const int CONTROL_SLIDER_CORRECTION = 211; - -// channelsettings::FFT_BANDWIDTH -// -// Bandwidth of the FFT display -uint32_t const channelsettings::FFT_BANDWIDTH = (400 KHz); - -// channelsettings::FFT_MINDB -// -// Maximum decibel level supported by the FFT -float const channelsettings::FFT_MAXDB = 4.0f; - -// channelsettings::FFT_MINDB -// -// Minimum decibel level supported by the FFT -float const channelsettings::FFT_MINDB = -72.0f; - -// is_platform_opengles (local) -// -// Helper function to determine if the platform is full OpenGL or OpenGL ES -static bool is_platform_opengles(void) -{ -#if defined(TARGET_WINDOWS) - return true; -#elif defined(TARGET_ANDROID) - return true; -#elif defined(TARGET_DARWIN_OSX) - return false; -#else - return (eglQueryAPI() == EGL_OPENGL_ES_API); -#endif -} - -//--------------------------------------------------------------------------- -// channelsettings::fftcontrol Constructor -// -// Arguments: -// -// window - Parent CWindow instance -// controlid - Identifier of the control within the parent CWindow instance - -channelsettings::fftcontrol::fftcontrol(kodi::gui::CWindow* window, int controlid) - : renderingcontrol(window, controlid) -{ - // Store some handy floating point copies of the width and height for rendering - m_widthf = static_cast(m_width); - m_heightf = static_cast(m_height); - - // Allocate the buffer to hold the scaled FFT data - m_fft = std::unique_ptr(new glm::vec2[m_width]); - - // Initialize the cut points - m_fftlowcut = m_ffthighcut = -1; - - // Create the model/view/projection matrix based on width and height - m_modelProjMat = glm::ortho(0.0f, m_widthf, m_heightf, 0.0f); - - // Create the vertex buffer object - glGenBuffers(1, &m_vertexVBO); -} - -//--------------------------------------------------------------------------- -// channelsettings::fftcontrol Destructor - -channelsettings::fftcontrol::~fftcontrol() -{ - // Delete the vertex buffer object - glDeleteBuffers(1, &m_vertexVBO); -} - -//--------------------------------------------------------------------------- -// channelsettings::fftcontrol::db_to_height (private) -// -// Converts a power level specified in decibels to a height for the FFT -// -// Arguments: -// -// db - Power level in decibels - -inline GLfloat channelsettings::fftcontrol::db_to_height(float db) const -{ - return m_heightf * ((db - channelsettings::FFT_MAXDB) / - (channelsettings::FFT_MINDB - channelsettings::FFT_MAXDB)); -} - -//--------------------------------------------------------------------------- -// channelsettings::fftcontrol::dirty (private) -// -// Indicates if there are dirty regions in the control that need to be rendered -// -// Arguments: -// -// NONE - -bool channelsettings::fftcontrol::dirty(void) -{ - return m_dirty; -} - -//--------------------------------------------------------------------------- -// channelsettings::fftcontrol::height -// -// Retrieves the height of the control -// -// Arguments: -// -// NONE - -size_t channelsettings::fftcontrol::height(void) const -{ - return static_cast(m_height); -} - -//--------------------------------------------------------------------------- -// channelsettings::fftcontrol::render (private) -// -// Renders all dirty regions within the control -// -// Arguments: -// -// NONE - -void channelsettings::fftcontrol::render(void) -{ - // Ensure that the shader was compiled properly before continuing - assert(m_shader.ShaderOK()); - if (!m_shader.ShaderOK()) - return; - - // Enable blending - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - // Enable the shader program - m_shader.EnableShader(); - - // Set the model/view/projection matrix - glUniformMatrix4fv(m_shader.uModelProjMatrix(), 1, GL_FALSE, glm::value_ptr(m_modelProjMat)); - - // Bind the vertex buffer object - glBindBuffer(GL_ARRAY_BUFFER, m_vertexVBO); - - // Enable the vertex array - glEnableVertexAttribArray(m_shader.aPosition()); - - // Set the vertex attribute pointer type - glVertexAttribPointer(m_shader.aPosition(), 2, GL_FLOAT, GL_FALSE, sizeof(glm::vec2), - BUFFER_OFFSET(offsetof(glm::vec2, x))); - -#ifndef HAS_ANGLE - // Background - glm::vec2 backgroundrect[4] = { - {0.0f, 0.0f}, {0.0f, m_heightf}, {m_widthf, 0.0f}, {m_widthf, m_heightf}}; - render_rect(glm::vec3(0.0f, 0.0f, 0.0f), backgroundrect); -#endif - - // 0dB level - GLfloat zerodb = db_to_height(0.0f); - glm::vec2 zerodbline[2] = {{0.0f, zerodb}, {m_widthf, zerodb}}; - render_line(glm::vec4(1.0f, 1.0f, 0.0f, 0.75f), zerodbline); - - // -6dB increment levels - for (int index = -6; index >= static_cast(channelsettings::FFT_MINDB); index -= 6) - { - - GLfloat y = db_to_height(static_cast(index)); - glm::vec2 dbline[2] = {{0.0f, y}, {m_widthf, y}}; - render_line(glm::vec4(1.0f, 1.0f, 1.0f, 0.2f), dbline); - } - - // Power range - glm::vec2 powerrect[4] = { - {0.0f, m_power}, {m_widthf, m_power}, {0.0f, m_noise}, {m_widthf, m_noise}}; - render_rect(glm::vec4(0.0f, 1.0f, 0.0f, 0.1f), powerrect); - glm::vec2 powerline[2] = {{0.0f, m_power}, {m_widthf, m_power}}; - render_line(glm::vec4(0.0f, 1.0f, 0.0f, 0.75f), powerline); - - // Noise range - glm::vec2 noiserect[4] = { - {0.0f, m_noise}, {m_widthf, m_noise}, {0.0f, m_heightf}, {m_widthf, m_heightf}}; - render_rect(glm::vec4(1.0f, 0.0f, 0.0f, 0.15f), noiserect); - glm::vec2 noiseline[2] = {{0.0f, m_noise}, {m_width, m_noise}}; - render_line(glm::vec4(1.0f, 0.0f, 0.0f, 0.75f), noiseline); - - // Center frequency - glm::vec2 centerline[2] = {{m_widthf / 2.0f, 0.0f}, {m_widthf / 2.0f, m_heightf}}; - render_line(glm::vec4(1.0f, 1.0f, 0.0f, 0.75f), centerline); - - // Low cut - glm::vec2 lowcutline[2] = {{static_cast(m_fftlowcut), 0.0f}, - {static_cast(m_fftlowcut), m_heightf}}; - render_line(glm::vec4(1.0f, 1.0f, 1.0f, 0.4f), lowcutline); - - // High cut - glm::vec2 highcutline[2] = {{static_cast(m_ffthighcut), 0.0f}, - {static_cast(m_ffthighcut), m_heightf}}; - render_line(glm::vec4(1.0f, 1.0f, 1.0f, 0.4f), highcutline); - - // FFT - glm::vec3 fftcolor(0.5f, 0.5f, 0.5f); - if (m_overload) - fftcolor = glm::vec3(1.0f, 0.0f, 0.0f); - else if (m_signallock) - { - - // If there is also a lock on the multiplex use a green line - if (m_muxlock) - fftcolor = glm::vec3(0.2823f, 0.7333f, 0.0901f); // Kelly Green (#4CBB17) - else - fftcolor = glm::vec3(1.0f, 1.0f, 1.0f); - } - - render_line_strip(fftcolor, m_fft.get(), m_width); - - glDisableVertexAttribArray(m_shader.aPosition()); // Disable the vertex array - glBindBuffer(GL_ARRAY_BUFFER, 0); // Unbind the VBO - - m_shader.DisableShader(); // Disable the shader program - glDisable(GL_BLEND); // Disable blending - - // Render state is clean until the next update from the meter instance - m_dirty = false; -} - -//--------------------------------------------------------------------------- -// channelsettings::fftcontrol::render_line (private) -// -// Renders a line primitive -// -// Arguments: -// -// color - Color value to use when rendering -// vertices - Line vertices - -void channelsettings::fftcontrol::render_line(glm::vec3 color, glm::vec2 vertices[2]) const -{ - return render_line(glm::vec4(color, 1.0f), vertices); -} - -//--------------------------------------------------------------------------- -// channelsettings::fftcontrol::render_line (private) -// -// Renders a line primitive -// -// Arguments: -// -// color - Color value to use when rendering -// vertices - Line vertices - -void channelsettings::fftcontrol::render_line(glm::vec4 color, glm::vec2 vertices[2]) const -{ - // Set the specific color value before drawing the line - glUniform4f(m_shader.uColor(), color.r, color.g, color.b, color.a); - - glm::vec2 p(vertices[1].x - vertices[0].x, vertices[1].y - vertices[0].y); - p = glm::normalize(p); - - // Pre-calculate the required deltas for the line thickness -#if defined(WIN32) && defined(HAS_ANGLE) - GLfloat const dx = (m_linewidthf); - GLfloat const dy = (m_lineheightf); -#else - GLfloat const dx = (m_linewidthf / 2.0f); - GLfloat const dy = (m_lineheightf / 2.0f); -#endif - - glm::vec2 const p1(-p.y, p.x); - glm::vec2 const p2(p.y, -p.x); - - glm::vec2 strip[] = { - - {vertices[0].x + p1.x * dx, vertices[0].y + p1.y * dy}, - {vertices[0].x + p2.x * dx, vertices[0].y + p2.y * dy}, - {vertices[1].x + p1.x * dx, vertices[1].y + p1.y * dy}, - {vertices[1].x + p2.x * dx, vertices[1].y + p2.y * dy}}; - - glBufferData(GL_ARRAY_BUFFER, (sizeof(glm::vec2) * 4), &strip[0], GL_STATIC_DRAW); - glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); -} - -//--------------------------------------------------------------------------- -// channelsettings::fftcontrol::render_line_strip (private) -// -// Renders a line strip primitive -// -// Arguments: -// -// color - Color value to use when rendering -// vertices - Line strip vertices -// numvertices - Number of line strip vertices - -void channelsettings::fftcontrol::render_line_strip(glm::vec3 color, - glm::vec2 vertices[], - size_t numvertices) const -{ - return render_line_strip(glm::vec4(color, 1.0f), vertices, numvertices); -} - -//--------------------------------------------------------------------------- -// channelsettings::fftcontrol::render_line_strip (private) -// -// Renders a line strip primitive -// -// Arguments: -// -// color - Color value to use when rendering -// vertices - Line vertices -// numvertices - Number of line strip vertices - -void channelsettings::fftcontrol::render_line_strip(glm::vec4 color, - glm::vec2 vertices[], - size_t numvertices) const -{ - // Set the specific color value before drawing the line - glUniform4f(m_shader.uColor(), color.r, color.g, color.b, color.a); - - // Each point in the line strip will be represented by six vertices in the triangle strip - std::unique_ptr strip(new glm::vec2[numvertices * 6]); - size_t strippos = 0; - - // Pre-calculate the required deltas for the line thickness -#if defined(WIN32) && defined(HAS_ANGLE) - GLfloat const dx = (m_linewidthf); - GLfloat const dy = (m_lineheightf); -#else - GLfloat const dx = (m_linewidthf / 2.0f); - GLfloat const dy = (m_lineheightf / 2.0f); -#endif - - for (size_t index = 0; index < numvertices - 1; index++) - { - glm::vec2 const& a = vertices[index]; - glm::vec2 const& b = vertices[index + 1]; - - glm::vec2 p(b.x - a.x, b.y - a.y); - p = glm::normalize(p); - - glm::vec2 const p1(-p.y, p.x); - glm::vec2 const p2(p.y, -p.x); - - strip[strippos++] = a; - strip[strippos++] = b; - strip[strippos++] = glm::vec2(a.x + p1.x * dx, a.y + p1.y * dy); - strip[strippos++] = glm::vec2(a.x + p2.x * dx, a.y + p2.y * dy); - strip[strippos++] = glm::vec2(b.x + p1.x * dx, b.y + p1.y * dy); - strip[strippos++] = glm::vec2(b.x + p2.x * dx, b.y + p2.y * dy); - } - - glBufferData(GL_ARRAY_BUFFER, (sizeof(glm::vec2) * strippos), strip.get(), GL_STATIC_DRAW); - glDrawArrays(GL_TRIANGLE_STRIP, 0, static_cast(strippos)); -} - -//--------------------------------------------------------------------------- -// channelsettings::fftcontrol::render_rect (private) -// -// Renders a line primitive -// -// Arguments: -// -// color - Color value to use when rendering -// vertices - Rectangle vertices - -void channelsettings::fftcontrol::render_rect(glm::vec3 color, glm::vec2 vertices[4]) const -{ - return render_rect(glm::vec4(color, 1.0f), vertices); -} - -//--------------------------------------------------------------------------- -// channelsettings::fftcontrol::render_rect (private) -// -// Renders a line primitive -// -// Arguments: -// -// color - Color value to use when rendering -// vertices - Rectangle vertices - -void channelsettings::fftcontrol::render_rect(glm::vec4 color, glm::vec2 vertices[4]) const -{ - // Set the specific color value before drawing the line - glUniform4f(m_shader.uColor(), color.r, color.g, color.b, color.a); - - // Render the rectangle as a 4-vertex GL_TRIANGLE_STRIP primitive - glBufferData(GL_ARRAY_BUFFER, (sizeof(glm::vec2) * 4), &vertices[0], GL_STATIC_DRAW); - glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); -} - -//--------------------------------------------------------------------------- -// channelsettings::fftcontrol::render_triangle (private) -// -// Renders a line primitive -// -// Arguments: -// -// color - Color value to use when rendering -// vertices - Triangle vertices - -void channelsettings::fftcontrol::render_triangle(glm::vec3 color, glm::vec2 vertices[3]) const -{ - return render_triangle(glm::vec4(color, 1.0f), vertices); -} - -//--------------------------------------------------------------------------- -// channelsettings::fftcontrol::render_triangle (private) -// -// Renders a line primitive -// -// Arguments: -// -// color - Color value to use when rendering -// vertices - Triangle vertices - -void channelsettings::fftcontrol::render_triangle(glm::vec4 color, glm::vec2 vertices[3]) const -{ - // Set the specific color value before drawing the line - glUniform4f(m_shader.uColor(), color.r, color.g, color.b, color.a); - - // Render the triangle as a 3-vertex GL_TRIANGLES primitive - glBufferData(GL_ARRAY_BUFFER, (sizeof(glm::vec2) * 3), &vertices[0], GL_STATIC_DRAW); - glDrawArrays(GL_TRIANGLES, 0, 3); -} - -//--------------------------------------------------------------------------- -// channelsettings::fftcontrol::update -// -// Updates the rendering control state and flags it as dirty -// -// Arguments: -// -// status - Signal meter status -// signallock - Flag indicating a signal lock -// muxlock - Flag indicating a multiplex lock - -void channelsettings::fftcontrol::update(struct signalmeter::signal_status const& status, - bool signallock, - bool muxlock) -{ - // Power and noise values are supplied as dB and need to be scaled to the viewport - m_power = db_to_height(status.power); - m_noise = db_to_height(status.noise); - - // The low and high cuts are provided as indexes into the plot data - m_fftlowcut = status.lowcut; - m_ffthighcut = status.highcut; - - // The length of the fft data should match the width of the control, but watch for overrruns - assert(status.plotsize == m_width); - size_t length = std::min(status.plotsize, static_cast(m_width)); - - // The FFT data merely needs to be converted into an X,Y vertex to be used by the renderer - for (size_t index = 0; index < length; index++) - m_fft[index] = glm::vec2(static_cast(index), static_cast(status.plotdata[index])); - - // The FFT line strip will be shown in a different color based on this information - m_overload = status.overload; - m_signallock = signallock; - m_muxlock = muxlock; - - // In the event of an FFT data underrun, flat-line the remainder of the data points - if (m_width > status.plotsize) - { - - for (size_t index = length; index < m_width; index++) - m_fft[index] = glm::vec2(static_cast(index), m_heightf); - } - - m_dirty = true; // Scene needs to be re-rendered -} - -//--------------------------------------------------------------------------- -// channelsettings::fftcontrol::width -// -// Retrieves the width of the control -// -// Arguments: -// -// NONE - -size_t channelsettings::fftcontrol::width(void) const -{ - return static_cast(m_width); -} - -//--------------------------------------------------------------------------- -// channelsettings::fftshader Constructor -// -// Arguments: -// -// NONE - -channelsettings::fftshader::fftshader() -{ - // VERTEX SHADER - std::string const vertexshader( - R"( -uniform mat4 u_modelViewProjectionMatrix; - -#ifdef GL_ES -attribute vec2 a_position; -#else -in vec2 a_position; -#endif - -void main() -{ - gl_Position = u_modelViewProjectionMatrix * vec4(a_position, 0.0, 1.0); -} - )"); - - // FRAGMENT SHADER - std::string const fragmentshader( - R"( -#ifdef GL_ES -precision mediump float; -#else -precision highp float; -#endif - -uniform vec4 u_color; - -#ifndef GL_ES -out vec4 FragColor; -#endif - -void main() -{ -#ifdef GL_ES - gl_FragColor = u_color; -#else - FragColor = u_color; -#endif -} - )"); - - // Compile and link the shader programs during construction - if (is_platform_opengles()) - CompileAndLink("#version 100\n", vertexshader, "#version 100\n", fragmentshader); - else - CompileAndLink("#version 150\n", vertexshader, "#version 150\n", fragmentshader); -} - -//--------------------------------------------------------------------------- -// channelsettings::fftshader::aPosition (const) -// -// Gets the location of the aPosition shader variable -// -// Arguments: -// -// NONE - -GLint channelsettings::fftshader::aPosition(void) const -{ - assert(m_aPosition != -1); - return m_aPosition; -} - -//--------------------------------------------------------------------------- -// channelsettings::fftshader::OnCompiledAndLinked (CShaderProgram) -// -// Invoked when the shader has been compiled and linked -// -// Arguments: -// -// NONE - -void channelsettings::fftshader::OnCompiledAndLinked(void) -{ - m_aPosition = glGetAttribLocation(ProgramHandle(), "a_position"); - m_uColor = glGetUniformLocation(ProgramHandle(), "u_color"); - m_uModelProjMatrix = glGetUniformLocation(ProgramHandle(), "u_modelViewProjectionMatrix"); -} - -//--------------------------------------------------------------------------- -// channelsettings::fftshader::OnDisabled (CShaderProgram) -// -// Invoked when the shader has been disabled -// -// Arguments: -// -// NONE - -void channelsettings::fftshader::OnDisabled(void) -{ -} - -//--------------------------------------------------------------------------- -// channelsettings::fftshader::OnEnabled (CShaderProgram) -// -// Invoked when the shader has been enabled -// -// Arguments: -// -// NONE - -bool channelsettings::fftshader::OnEnabled(void) -{ - return true; -} - -//--------------------------------------------------------------------------- -// channelsettings::fftshader::uColor (const) -// -// Gets the location of the uColor shader variable -// -// Arguments: -// -// NONE - -GLint channelsettings::fftshader::uColor(void) const -{ - assert(m_uColor != -1); - return m_uColor; -} - -//--------------------------------------------------------------------------- -// channelsettings::fftshader::uModelProjMatrix (const) -// -// Gets the location of the aPosition shader variable -// -// Arguments: -// -// NONE - -GLint channelsettings::fftshader::uModelProjMatrix(void) const -{ - assert(m_uModelProjMatrix != -1); - return m_uModelProjMatrix; -} - -//--------------------------------------------------------------------------- -// channelsettings Constructor (private) -// -// Arguments: -// -// device - Device instance -// tunerprops - Tuner properties -// channelprops - Channel properties - -channelsettings::channelsettings(std::unique_ptr device, - struct tunerprops const& tunerprops, - struct channelprops const& channelprops, - bool isnew) - : kodi::gui::CWindow("channelsettings.xml", "skin.estuary", true), - m_device(std::move(device)), - m_tunerprops(tunerprops), - m_channelprops(channelprops), - m_isnew(isnew) -{ - assert(m_device); - - m_signalprops.filter = false; // Never apply the filter here - - // Analog FM - // - if (channelprops.modulation == modulation::fm) - { - - m_signalprops.samplerate = 1600 KHz; - m_signalprops.bandwidth = 220 KHz; - m_signalprops.lowcut = -103 KHz; - m_signalprops.highcut = 103 KHz; - - // Analog signals require a DC offset to be applied to prevent a natural - // spike from occurring at the center frequency on many RTL-SDR devices - m_signalprops.offset = (m_signalprops.samplerate / 4); - } - - // HD Radio - // - else if (channelprops.modulation == modulation::hd) - { - - m_signalprops.samplerate = 1488375; - m_signalprops.bandwidth = 440 KHz; - m_signalprops.lowcut = -204 KHz; - m_signalprops.highcut = 204 KHz; - m_signalprops.offset = 0; - } - - // DAB Ensemble - // - else if (channelprops.modulation == modulation::dab) - { - - m_signalprops.samplerate = 2048 KHz; - m_signalprops.bandwidth = 1712 KHz; - m_signalprops.lowcut = -780 KHz; - m_signalprops.highcut = 780 KHz; - m_signalprops.offset = 0; - } - - // Weather Radio - // - else if (channelprops.modulation == modulation::wx) - { - - m_signalprops.samplerate = 1600 KHz; - m_signalprops.bandwidth = 200 KHz; - m_signalprops.lowcut = -8 KHz; - m_signalprops.highcut = 8 KHz; - - // Analog signals require a DC offset to be applied to prevent a natural - // spike from occurring at the center frequency on many RTL-SDR devices - m_signalprops.offset = (m_signalprops.samplerate / 4); - } - - else - throw string_exception("unknown channel modulation"); - - // Get the valid manual gain values supported by the device - m_device->get_valid_gains(m_manualgains); - - // Set the device to match the channel properties at time of construction (prior to OnCreate) - m_device->set_center_frequency(m_channelprops.frequency + m_signalprops.offset); - m_device->set_frequency_correction(m_tunerprops.freqcorrection + m_channelprops.freqcorrection); - m_device->set_sample_rate(m_signalprops.samplerate); - m_device->set_automatic_gain_control(m_channelprops.autogain); - if (!m_channelprops.autogain) - m_device->set_gain(m_channelprops.manualgain); -} - -//--------------------------------------------------------------------------- -// channelsettings Destructor - -channelsettings::~channelsettings() -{ - m_stop = true; // Signal worker thread to stop - if (m_device) - m_device->cancel_async(); // Cancel any async read operations - if (m_worker.joinable()) - m_worker.join(); // Wait for thread to exit - m_device.reset(); // Release RTL-SDR device -} - -//--------------------------------------------------------------------------- -// channelsettings::close -// -// Closes the dialog box -// -// Arguments: -// -// result - Result value from the dialog box operation - -void channelsettings::close(bool result) -{ - m_result = result; // Set the result code - Close(); // Close the dialog box -} - -//--------------------------------------------------------------------------- -// channelsettings::create (static) -// -// Factory method, creates a new channelsettings instance -// -// Arguments: -// -// device - Device instance -// tunerprops - Tuner properties -// channelprops - Channel properties - -std::unique_ptr channelsettings::create(std::unique_ptr device, - struct tunerprops const& tunerprops, - struct channelprops const& channelprops) -{ - return create(std::move(device), tunerprops, channelprops, false); -} - -//--------------------------------------------------------------------------- -// channelsettings::create (static) -// -// Factory method, creates a new channelsettings instance -// -// Arguments: -// -// device - Device instance -// tunerprops - Tuner properties -// channelprops - Channel properties -// isnew - Flag indicating if this is a new channel - -std::unique_ptr channelsettings::create(std::unique_ptr device, - struct tunerprops const& tunerprops, - struct channelprops const& channelprops, - bool isnew) -{ - return std::unique_ptr( - new channelsettings(std::move(device), tunerprops, channelprops, isnew)); -} - -//--------------------------------------------------------------------------- -// channelsettings::gain_to_percent (private) -// -// Converts a manual gain value into a percentage -// -// Arguments: -// -// gain - Gain to convert - -int channelsettings::gain_to_percent(int gain) const -{ - if (m_manualgains.empty()) - return 0; - - // Convert the gain into something that's valid for the tuner - gain = nearest_valid_gain(gain); - - // Use the index within the gain table to generate the percentage - for (size_t index = 0; index < m_manualgains.size(); index++) - { - - if (gain == m_manualgains[index]) - return static_cast((index * 100) / (m_manualgains.size() - 1)); - } - - return 0; -} - -//--------------------------------------------------------------------------- -// channelsettings::get_channel_properties -// -// Gets the updated channel properties from the dialog box -// -// Arguments: -// -// channelprops - Structure to receive the updated channel properties - -void channelsettings::get_channel_properties(struct channelprops& channelprops) const -{ - channelprops = m_channelprops; -} - -//--------------------------------------------------------------------------- -// channelsettings::get_dialog_result -// -// Gets the result code from the dialog box -// -// Arguments: -// -// NONE - -bool channelsettings::get_dialog_result(void) const -{ - return m_result; -} - -//--------------------------------------------------------------------------- -// channelsettings::get_subchannel_properties -// -// Gets the updated subchannel properties from the dialog box -// -// Arguments: -// -// subchannelprops - vector<> to receive the subchannel properties - -void channelsettings::get_subchannel_properties( - std::vector& subchannelprops) const -{ - subchannelprops.clear(); // Ensure the vector<> is empty - - // Copy the known subchannel information into the output vector<> - for (auto const& it : m_muxdata.subchannels) - subchannelprops.push_back({it.number, it.name}); - - // Sort the vector<> by the subchannel number before returning - std::sort(subchannelprops.begin(), subchannelprops.end(), - [](auto const& lhs, auto const& rhs) -> bool { return lhs.number < rhs.number; }); -} - -//--------------------------------------------------------------------------- -// channelsettings::meter_status (private) -// -// Updates the state of the signal meter -// -// Arguments: -// -// status - Updated signal status from the signal meter - -void channelsettings::meter_status(struct signalmeter::signal_status const& status) -{ - std::unique_lock lock(m_muxdatalock); // Synchronization object - bool signallock = true; // Signal lock - bool muxlock = false; // Multiplex lock - char strbuf[64] = {}; // snprintf() text buffer - - // For digital signals we can determine signal lock and multiplex lock values - if (m_muxscanner) - { - - signallock = m_muxdata.sync; - if (signallock) - { - - // A multiplex lock occurs when a name has been assigned to the multiplex - // as well as all of the detected subchannels within that multiplex - if ((!m_muxdata.name.empty()) && (m_muxdata.subchannels.size() > 0)) - { - - muxlock = true; - for (auto const& iterator : m_muxdata.subchannels) - if (iterator.name.empty()) - { - muxlock = false; - break; - } - } - } - } - - lock.unlock(); // Release multiplex data lock - - // Signal Meter - // - m_render_signalmeter->update(status, signallock, muxlock); - - // Signal Strength - // - if (!std::isnan(status.power)) - { - - snprintf(strbuf, std::extent::value, "%.1F dB", status.power); - m_edit_signalpower->SetText(strbuf); - } - else - m_edit_signalpower->SetText("N/A"); - - // Signal-to-noise - // - if (!std::isnan(status.snr)) - { - - snprintf(strbuf, std::extent::value, "%d dB", static_cast(status.snr)); - m_edit_signalsnr->SetText(strbuf); - } - else - m_edit_signalsnr->SetText("N/A"); -} - -//--------------------------------------------------------------------------- -// channelsettings::mux_data (private) -// -// Updates the state of the multiplex data -// -// Arguments: -// -// muxdata - Updated multiplex data from the mux scanner - -void channelsettings::mux_data(struct muxscanner::multiplex const& muxdata) -{ - std::unique_lock lock(m_muxdatalock); - - m_muxdata = muxdata; // Copy the updated mutex information - - // Change the channel name automatically if the channel is new - if (m_isnew && !m_muxdata.name.empty()) - { - - m_channelprops.name = m_muxdata.name; - m_edit_channelname->SetText(m_channelprops.name); - } -} - -//--------------------------------------------------------------------------- -// channelsettings::nearest_valid_gain (private) -// -// Gets the closest valid value for a manual gain setting -// -// Arguments: -// -// gain - Gain to adjust - -int channelsettings::nearest_valid_gain(int gain) const -{ - if (m_manualgains.empty()) - return 0; - - // Select the gain value that's closest to what has been requested - int nearest = m_manualgains[0]; - for (size_t index = 0; index < m_manualgains.size(); index++) - { - - if (std::abs(gain - m_manualgains[index]) < std::abs(gain - nearest)) - nearest = m_manualgains[index]; - } - - return nearest; -} - -//--------------------------------------------------------------------------- -// channelsettings::percent_to_gain (private) -// -// Converts a percentage into a manual gain value -// -// Arguments: -// -// percent - Percentage to convert - -int channelsettings::percent_to_gain(int percent) const -{ - if (m_manualgains.empty()) - return 0; - - if (percent == 0) - return m_manualgains.front(); - else if (percent == 100) - return m_manualgains.back(); - - return m_manualgains[(percent * m_manualgains.size()) / 100]; -} - -//--------------------------------------------------------------------------- -// channelsettings::update_gain (private) -// -// Updates the state of the gain control -// -// Arguments: -// -// NONE - -void channelsettings::update_gain(void) -{ - char strbuf[64] = {}; // snprintf() text buffer - - if (!m_channelprops.autogain) - { - - // Convert the gain value from tenths of a decibel into XX.X dB format - snprintf(strbuf, std::extent::value, "%.1f dB", - m_channelprops.manualgain / 10.0); - m_edit_signalgain->SetText(strbuf); - } - - else - m_edit_signalgain->SetText("Auto"); -} - -//--------------------------------------------------------------------------- -// channelsettings::worker (private) -// -// Worker thread procedure used to pump data into the signal meter -// -// Arguments: -// -// started - Condition variable to set when thread has started - -void channelsettings::worker(scalar_condition& started) -{ - assert(m_device); - assert(m_signalmeter); - - // read_callback_func (local) - // - // Asynchronous read callback function for the RTL-SDR device - auto read_callback_func = [&](uint8_t const* buffer, size_t count) -> void - { - // Feed the signal meter, and optionally the multiplex scanner - m_signalmeter->inputsamples(buffer, count); - if (m_muxscanner) - m_muxscanner->inputsamples(buffer, count); - }; - - // Begin streaming from the device and inform the caller that the thread is running - m_device->begin_stream(); - started = true; - - // Continuously read data from the device until cancel_async() has been called - try - { - m_device->read_async(read_callback_func, static_cast(32 KiB)); - } - catch (...) - { - m_worker_exception = std::current_exception(); - } - - m_stopped.store(true); // Thread is stopped -} - -//--------------------------------------------------------------------------- -// CWINDOW IMPLEMENTATION -//--------------------------------------------------------------------------- - -//--------------------------------------------------------------------------- -// channelsettings::OnAction (CWindow) -// -// Receives action codes that are sent to this window -// -// Arguments: -// -// actionId - The action id to perform - -bool channelsettings::OnAction(ADDON_ACTION actionId) -{ - return kodi::gui::CWindow::OnAction(actionId); -} - -//--------------------------------------------------------------------------- -// channelsettings::OnClick (CWindow) -// -// Receives click event notifications for a control -// -// controlId - GUI control identifier - -bool channelsettings::OnClick(int controlId) -{ - uint32_t browseheading = 30312; // "Browse for channel logo" - - // "Browse for multiplex logo" / "Browse for emsemble logo" - if (m_channelprops.modulation == modulation::hd) - browseheading = 30313; - else if (m_channelprops.modulation == modulation::dab) - browseheading = 30314; - - switch (controlId) - { - - case CONTROL_EDIT_CHANNELNAME: - m_channelprops.name = m_edit_channelname->GetText(); - break; - - case CONTROL_BUTTON_CHANNELICON: - kodi::gui::dialogs::FileBrowser::ShowAndGetImage( - "local|network|pictures", kodi::addon::GetLocalizedString(browseheading), - m_channelprops.logourl); - m_image_channelicon->SetFileName(m_channelprops.logourl, false); - return true; - - case CONTROL_RADIO_AUTOMATICGAIN: - m_channelprops.autogain = m_radio_autogain->IsSelected(); - m_device->set_automatic_gain_control(m_channelprops.autogain); - if (!m_channelprops.autogain) - m_device->set_gain(m_channelprops.manualgain); - m_slider_manualgain->SetEnabled(!m_channelprops.autogain); - update_gain(); - return true; - - case CONTROL_SLIDER_MANUALGAIN: - m_channelprops.manualgain = - percent_to_gain(static_cast(m_slider_manualgain->GetPercentage())); - if (!m_channelprops.autogain) - m_device->set_gain(m_channelprops.manualgain); - update_gain(); - return true; - - case CONTROL_SLIDER_CORRECTION: - m_channelprops.freqcorrection = m_slider_correction->GetIntValue(); - m_device->set_frequency_correction(m_tunerprops.freqcorrection + - m_channelprops.freqcorrection); - return true; - - case CONTROL_BUTTON_OK: - close(true); - return true; - - case CONTROL_BUTTON_CANCEL: - close(false); - return true; - } - - return kodi::gui::CWindow::OnClick(controlId); -} - -//--------------------------------------------------------------------------- -// channelsettings::OnInit (CWindow) -// -// Called to initialize the window object -// -// Arguments: -// -// NONE - -bool channelsettings::OnInit(void) -{ - assert(m_device); - - try - { - - // Get references to all of the manipulable dialog controls - m_button_ok = std::unique_ptr(new CButton(this, CONTROL_BUTTON_OK)); - m_edit_frequency = std::unique_ptr(new CEdit(this, CONTROL_EDIT_FREQUENCY)); - m_edit_channelname = std::unique_ptr(new CEdit(this, CONTROL_EDIT_CHANNELNAME)); - m_edit_modulation = std::unique_ptr(new CEdit(this, CONTROL_EDIT_MODULATION)); - m_button_channelicon = std::unique_ptr(new CButton(this, CONTROL_BUTTON_CHANNELICON)); - m_image_channelicon = std::unique_ptr(new CImage(this, CONTROL_IMAGE_CHANNELICON)); - m_radio_autogain = - std::unique_ptr(new CRadioButton(this, CONTROL_RADIO_AUTOMATICGAIN)); - m_slider_manualgain = - std::unique_ptr(new CSettingsSlider(this, CONTROL_SLIDER_MANUALGAIN)); - m_slider_correction = - std::unique_ptr(new CSettingsSlider(this, CONTROL_SLIDER_CORRECTION)); - m_render_signalmeter = - std::unique_ptr(new fftcontrol(this, CONTROL_RENDER_SIGNALMETER)); - m_edit_signalgain = std::unique_ptr(new CEdit(this, CONTROL_EDIT_METERGAIN)); - m_edit_signalpower = std::unique_ptr(new CEdit(this, CONTROL_EDIT_METERPOWER)); - m_edit_signalsnr = std::unique_ptr(new CEdit(this, CONTROL_EDIT_METERSNR)); - - // Set the window title based on if this is a single channel or a multiplex/ensemble - std::unique_ptr headerlabel(new CLabel(this, CONTROL_LABEL_HEADERLABEL)); - headerlabel->SetLabel(kodi::addon::GetLocalizedString(30301)); - if (m_channelprops.modulation == modulation::hd) - headerlabel->SetLabel(kodi::addon::GetLocalizedString(30302)); - else if (m_channelprops.modulation == modulation::dab) - headerlabel->SetLabel(kodi::addon::GetLocalizedString(30303)); - - // Change the text of the OK button to "Add" if we are in new channel mode - if (m_isnew) - m_button_ok->SetLabel(kodi::addon::GetLocalizedString(15019)); - - // Set the channel frequency in XXX.X MHz (FM/HD) or XXX.XXX format (DAB/WX) - char freqstr[128]; - double frequency = (m_channelprops.frequency / static_cast(100000)) / 10.0; - if ((m_channelprops.modulation == modulation::dab) || - (m_channelprops.modulation == modulation::wx)) - snprintf(freqstr, std::extent::value, "%.3f MHz", frequency); - else - snprintf(freqstr, std::extent::value, "%.1f MHz", frequency); - m_edit_frequency->SetText(freqstr); - - // Set the channel name and logo/icon - m_edit_channelname->SetText(m_channelprops.name); - m_image_channelicon->SetFileName(m_channelprops.logourl, false); - - // Channel name is disabled for multiplex/ensemble modulations - if ((m_channelprops.modulation == modulation::hd) || - (m_channelprops.modulation == modulation::dab)) - m_edit_channelname->SetEnabled(false); - - // Set the modulation type - if (m_channelprops.modulation == modulation::fm) - m_edit_modulation->SetText(kodi::addon::GetLocalizedString(30304)); - else if (m_channelprops.modulation == modulation::hd) - m_edit_modulation->SetText(kodi::addon::GetLocalizedString(30305)); - else if (m_channelprops.modulation == modulation::dab) - m_edit_modulation->SetText(kodi::addon::GetLocalizedString(30306)); - else if (m_channelprops.modulation == modulation::wx) - m_edit_modulation->SetText(kodi::addon::GetLocalizedString(30307)); - - // Change the text of the Name edit for multiplex/ensemble channels (HD/DAB) - if (m_channelprops.modulation == modulation::hd) - m_edit_channelname->SetLabel(kodi::addon::GetLocalizedString(30308)); - else if (m_channelprops.modulation == modulation::dab) - m_edit_channelname->SetLabel(kodi::addon::GetLocalizedString(30309)); - - // Change the text of the Logo button for multiplex/ensemble channels (HD/DAB) - if (m_channelprops.modulation == modulation::hd) - m_button_channelicon->SetLabel(kodi::addon::GetLocalizedString(30310)); - else if (m_channelprops.modulation == modulation::dab) - m_button_channelicon->SetLabel(kodi::addon::GetLocalizedString(30311)); - - // Adjust the manual gain value to match something that the tuner supports - m_channelprops.manualgain = nearest_valid_gain(m_channelprops.manualgain); - - // Set the tuner gain parameters - m_radio_autogain->SetSelected(m_channelprops.autogain); - m_slider_manualgain->SetEnabled(!m_channelprops.autogain); - m_slider_manualgain->SetPercentage( - static_cast(gain_to_percent(m_channelprops.manualgain))); - update_gain(); - - // Set the frequency correction parameters - m_slider_correction->SetIntInterval(1); - m_slider_correction->SetIntRange(-41, 40); - m_slider_correction->SetIntValue(m_channelprops.freqcorrection); - - // Set the default text for the signal indicators - m_edit_signalpower->SetText("N/A"); - m_edit_signalsnr->SetText("N/A"); - - // Initialize the signal meter plot properties based on the size of the render control - struct signalplotprops plotprops = {}; - plotprops.height = m_render_signalmeter->height(); - plotprops.width = m_render_signalmeter->width(); - plotprops.mindb = FFT_MINDB; - plotprops.maxdb = FFT_MAXDB; - - // Create the signal meter instance with a 100ms callback rate - m_signalmeter = - signalmeter::create(m_signalprops, plotprops, 100, - std::bind(&channelsettings::meter_status, this, std::placeholders::_1)); - - // Create the multiplex scanner instance if applicable to the modulation - if (m_channelprops.modulation == modulation::hd) - m_muxscanner = - hdmuxscanner::create(m_signalprops.samplerate, m_channelprops.frequency, - std::bind(&channelsettings::mux_data, this, std::placeholders::_1)); - else if (m_channelprops.modulation == modulation::dab) - m_muxscanner = - dabmuxscanner::create(m_signalprops.samplerate, - std::bind(&channelsettings::mux_data, this, std::placeholders::_1)); - - // Create a worker thread on which to pump data into the signal meter - scalar_condition started{false}; - m_worker = std::thread(&channelsettings::worker, this, std::ref(started)); - started.wait_until_equals(true); - } - - catch (...) - { - return false; - } - - return kodi::gui::CWindow::OnInit(); -} - -//--------------------------------------------------------------------------- - -#pragma warning(pop) +//--------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//--------------------------------------------------------------------------- + +#include "channelsettings.h" + +#include "dabmuxscanner.h" +#include "hdmuxscanner.h" +#include "stdafx.h" +#include "string_exception.h" + +#include +#include +#include +#include +#include +#include + +#ifdef HAS_GLES +#include +#endif + +#pragma warning(push, 4) + +// Control Identifiers +// +static const int CONTROL_LABEL_HEADERLABEL = 2; +static const int CONTROL_BUTTON_OK = 100; +static const int CONTROL_BUTTON_CANCEL = 101; +static const int CONTROL_EDIT_FREQUENCY = 200; +static const int CONTROL_EDIT_CHANNELNAME = 201; +static const int CONTROL_BUTTON_CHANNELICON = 202; +static const int CONTROL_IMAGE_CHANNELICON = 203; +static const int CONTROL_RADIO_AUTOMATICGAIN = 204; +static const int CONTROL_SLIDER_MANUALGAIN = 205; +static const int CONTROL_RENDER_SIGNALMETER = 206; +static const int CONTROL_EDIT_METERGAIN = 207; +static const int CONTROL_EDIT_METERPOWER = 208; +static const int CONTROL_EDIT_METERSNR = 209; +static const int CONTROL_EDIT_MODULATION = 210; +static const int CONTROL_SLIDER_CORRECTION = 211; + +// channelsettings::FFT_BANDWIDTH +// +// Bandwidth of the FFT display +uint32_t const channelsettings::FFT_BANDWIDTH = (400 KHz); + +// channelsettings::FFT_MINDB +// +// Maximum decibel level supported by the FFT +float const channelsettings::FFT_MAXDB = 4.0f; + +// channelsettings::FFT_MINDB +// +// Minimum decibel level supported by the FFT +float const channelsettings::FFT_MINDB = -72.0f; + +// is_platform_opengles (local) +// +// Helper function to determine if the platform is full OpenGL or OpenGL ES +static bool is_platform_opengles(void) +{ +#if defined(TARGET_WINDOWS) + return true; +#elif defined(TARGET_ANDROID) + return true; +#elif defined(TARGET_DARWIN_OSX) + return false; +#else + return (eglQueryAPI() == EGL_OPENGL_ES_API); +#endif +} + +//--------------------------------------------------------------------------- +// channelsettings::fftcontrol Constructor +// +// Arguments: +// +// window - Parent CWindow instance +// controlid - Identifier of the control within the parent CWindow instance + +channelsettings::fftcontrol::fftcontrol(kodi::gui::CWindow* window, int controlid) + : renderingcontrol(window, controlid) +{ + // Store some handy floating point copies of the width and height for rendering + m_widthf = static_cast(m_width); + m_heightf = static_cast(m_height); + + // Allocate the buffer to hold the scaled FFT data + m_fft = std::unique_ptr(new glm::vec2[m_width]); + + // Initialize the cut points + m_fftlowcut = m_ffthighcut = -1; + + // Create the model/view/projection matrix based on width and height + m_modelProjMat = glm::ortho(0.0f, m_widthf, m_heightf, 0.0f); + + // Create the vertex buffer object + glGenBuffers(1, &m_vertexVBO); +} + +//--------------------------------------------------------------------------- +// channelsettings::fftcontrol Destructor + +channelsettings::fftcontrol::~fftcontrol() +{ + // Delete the vertex buffer object + glDeleteBuffers(1, &m_vertexVBO); +} + +//--------------------------------------------------------------------------- +// channelsettings::fftcontrol::db_to_height (private) +// +// Converts a power level specified in decibels to a height for the FFT +// +// Arguments: +// +// db - Power level in decibels + +inline GLfloat channelsettings::fftcontrol::db_to_height(float db) const +{ + return m_heightf * ((db - channelsettings::FFT_MAXDB) / + (channelsettings::FFT_MINDB - channelsettings::FFT_MAXDB)); +} + +//--------------------------------------------------------------------------- +// channelsettings::fftcontrol::dirty (private) +// +// Indicates if there are dirty regions in the control that need to be rendered +// +// Arguments: +// +// NONE + +bool channelsettings::fftcontrol::dirty(void) +{ + return m_dirty; +} + +//--------------------------------------------------------------------------- +// channelsettings::fftcontrol::height +// +// Retrieves the height of the control +// +// Arguments: +// +// NONE + +size_t channelsettings::fftcontrol::height(void) const +{ + return static_cast(m_height); +} + +//--------------------------------------------------------------------------- +// channelsettings::fftcontrol::render (private) +// +// Renders all dirty regions within the control +// +// Arguments: +// +// NONE + +void channelsettings::fftcontrol::render(void) +{ + // Ensure that the shader was compiled properly before continuing + assert(m_shader.ShaderOK()); + if (!m_shader.ShaderOK()) + return; + + // Enable blending + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + // Enable the shader program + m_shader.EnableShader(); + + // Set the model/view/projection matrix + glUniformMatrix4fv(m_shader.uModelProjMatrix(), 1, GL_FALSE, glm::value_ptr(m_modelProjMat)); + + // Bind the vertex buffer object + glBindBuffer(GL_ARRAY_BUFFER, m_vertexVBO); + + // Enable the vertex array + glEnableVertexAttribArray(m_shader.aPosition()); + + // Set the vertex attribute pointer type + glVertexAttribPointer(m_shader.aPosition(), 2, GL_FLOAT, GL_FALSE, sizeof(glm::vec2), + BUFFER_OFFSET(offsetof(glm::vec2, x))); + +#ifndef HAS_ANGLE + // Background + glm::vec2 backgroundrect[4] = { + {0.0f, 0.0f}, {0.0f, m_heightf}, {m_widthf, 0.0f}, {m_widthf, m_heightf}}; + render_rect(glm::vec3(0.0f, 0.0f, 0.0f), backgroundrect); +#endif + + // 0dB level + GLfloat zerodb = db_to_height(0.0f); + glm::vec2 zerodbline[2] = {{0.0f, zerodb}, {m_widthf, zerodb}}; + render_line(glm::vec4(1.0f, 1.0f, 0.0f, 0.75f), zerodbline); + + // -6dB increment levels + for (int index = -6; index >= static_cast(channelsettings::FFT_MINDB); index -= 6) + { + + GLfloat y = db_to_height(static_cast(index)); + glm::vec2 dbline[2] = {{0.0f, y}, {m_widthf, y}}; + render_line(glm::vec4(1.0f, 1.0f, 1.0f, 0.2f), dbline); + } + + // Power range + glm::vec2 powerrect[4] = { + {0.0f, m_power}, {m_widthf, m_power}, {0.0f, m_noise}, {m_widthf, m_noise}}; + render_rect(glm::vec4(0.0f, 1.0f, 0.0f, 0.1f), powerrect); + glm::vec2 powerline[2] = {{0.0f, m_power}, {m_widthf, m_power}}; + render_line(glm::vec4(0.0f, 1.0f, 0.0f, 0.75f), powerline); + + // Noise range + glm::vec2 noiserect[4] = { + {0.0f, m_noise}, {m_widthf, m_noise}, {0.0f, m_heightf}, {m_widthf, m_heightf}}; + render_rect(glm::vec4(1.0f, 0.0f, 0.0f, 0.15f), noiserect); + glm::vec2 noiseline[2] = {{0.0f, m_noise}, {m_width, m_noise}}; + render_line(glm::vec4(1.0f, 0.0f, 0.0f, 0.75f), noiseline); + + // Center frequency + glm::vec2 centerline[2] = {{m_widthf / 2.0f, 0.0f}, {m_widthf / 2.0f, m_heightf}}; + render_line(glm::vec4(1.0f, 1.0f, 0.0f, 0.75f), centerline); + + // Low cut + glm::vec2 lowcutline[2] = {{static_cast(m_fftlowcut), 0.0f}, + {static_cast(m_fftlowcut), m_heightf}}; + render_line(glm::vec4(1.0f, 1.0f, 1.0f, 0.4f), lowcutline); + + // High cut + glm::vec2 highcutline[2] = {{static_cast(m_ffthighcut), 0.0f}, + {static_cast(m_ffthighcut), m_heightf}}; + render_line(glm::vec4(1.0f, 1.0f, 1.0f, 0.4f), highcutline); + + // FFT + glm::vec3 fftcolor(0.5f, 0.5f, 0.5f); + if (m_overload) + fftcolor = glm::vec3(1.0f, 0.0f, 0.0f); + else if (m_signallock) + { + + // If there is also a lock on the multiplex use a green line + if (m_muxlock) + fftcolor = glm::vec3(0.2823f, 0.7333f, 0.0901f); // Kelly Green (#4CBB17) + else + fftcolor = glm::vec3(1.0f, 1.0f, 1.0f); + } + + render_line_strip(fftcolor, m_fft.get(), m_width); + + glDisableVertexAttribArray(m_shader.aPosition()); // Disable the vertex array + glBindBuffer(GL_ARRAY_BUFFER, 0); // Unbind the VBO + + m_shader.DisableShader(); // Disable the shader program + glDisable(GL_BLEND); // Disable blending + + // Render state is clean until the next update from the meter instance + m_dirty = false; +} + +//--------------------------------------------------------------------------- +// channelsettings::fftcontrol::render_line (private) +// +// Renders a line primitive +// +// Arguments: +// +// color - Color value to use when rendering +// vertices - Line vertices + +void channelsettings::fftcontrol::render_line(glm::vec3 color, glm::vec2 vertices[2]) const +{ + return render_line(glm::vec4(color, 1.0f), vertices); +} + +//--------------------------------------------------------------------------- +// channelsettings::fftcontrol::render_line (private) +// +// Renders a line primitive +// +// Arguments: +// +// color - Color value to use when rendering +// vertices - Line vertices + +void channelsettings::fftcontrol::render_line(glm::vec4 color, glm::vec2 vertices[2]) const +{ + // Set the specific color value before drawing the line + glUniform4f(m_shader.uColor(), color.r, color.g, color.b, color.a); + + glm::vec2 p(vertices[1].x - vertices[0].x, vertices[1].y - vertices[0].y); + p = glm::normalize(p); + + // Pre-calculate the required deltas for the line thickness +#if defined(WIN32) && defined(HAS_ANGLE) + GLfloat const dx = (m_linewidthf); + GLfloat const dy = (m_lineheightf); +#else + GLfloat const dx = (m_linewidthf / 2.0f); + GLfloat const dy = (m_lineheightf / 2.0f); +#endif + + glm::vec2 const p1(-p.y, p.x); + glm::vec2 const p2(p.y, -p.x); + + glm::vec2 strip[] = { + + {vertices[0].x + p1.x * dx, vertices[0].y + p1.y * dy}, + {vertices[0].x + p2.x * dx, vertices[0].y + p2.y * dy}, + {vertices[1].x + p1.x * dx, vertices[1].y + p1.y * dy}, + {vertices[1].x + p2.x * dx, vertices[1].y + p2.y * dy}}; + + glBufferData(GL_ARRAY_BUFFER, (sizeof(glm::vec2) * 4), &strip[0], GL_STATIC_DRAW); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); +} + +//--------------------------------------------------------------------------- +// channelsettings::fftcontrol::render_line_strip (private) +// +// Renders a line strip primitive +// +// Arguments: +// +// color - Color value to use when rendering +// vertices - Line strip vertices +// numvertices - Number of line strip vertices + +void channelsettings::fftcontrol::render_line_strip(glm::vec3 color, + glm::vec2 vertices[], + size_t numvertices) const +{ + return render_line_strip(glm::vec4(color, 1.0f), vertices, numvertices); +} + +//--------------------------------------------------------------------------- +// channelsettings::fftcontrol::render_line_strip (private) +// +// Renders a line strip primitive +// +// Arguments: +// +// color - Color value to use when rendering +// vertices - Line vertices +// numvertices - Number of line strip vertices + +void channelsettings::fftcontrol::render_line_strip(glm::vec4 color, + glm::vec2 vertices[], + size_t numvertices) const +{ + // Set the specific color value before drawing the line + glUniform4f(m_shader.uColor(), color.r, color.g, color.b, color.a); + + // Each point in the line strip will be represented by six vertices in the triangle strip + std::unique_ptr strip(new glm::vec2[numvertices * 6]); + size_t strippos = 0; + + // Pre-calculate the required deltas for the line thickness +#if defined(WIN32) && defined(HAS_ANGLE) + GLfloat const dx = (m_linewidthf); + GLfloat const dy = (m_lineheightf); +#else + GLfloat const dx = (m_linewidthf / 2.0f); + GLfloat const dy = (m_lineheightf / 2.0f); +#endif + + for (size_t index = 0; index < numvertices - 1; index++) + { + glm::vec2 const& a = vertices[index]; + glm::vec2 const& b = vertices[index + 1]; + + glm::vec2 p(b.x - a.x, b.y - a.y); + p = glm::normalize(p); + + glm::vec2 const p1(-p.y, p.x); + glm::vec2 const p2(p.y, -p.x); + + strip[strippos++] = a; + strip[strippos++] = b; + strip[strippos++] = glm::vec2(a.x + p1.x * dx, a.y + p1.y * dy); + strip[strippos++] = glm::vec2(a.x + p2.x * dx, a.y + p2.y * dy); + strip[strippos++] = glm::vec2(b.x + p1.x * dx, b.y + p1.y * dy); + strip[strippos++] = glm::vec2(b.x + p2.x * dx, b.y + p2.y * dy); + } + + glBufferData(GL_ARRAY_BUFFER, (sizeof(glm::vec2) * strippos), strip.get(), GL_STATIC_DRAW); + glDrawArrays(GL_TRIANGLE_STRIP, 0, static_cast(strippos)); +} + +//--------------------------------------------------------------------------- +// channelsettings::fftcontrol::render_rect (private) +// +// Renders a line primitive +// +// Arguments: +// +// color - Color value to use when rendering +// vertices - Rectangle vertices + +void channelsettings::fftcontrol::render_rect(glm::vec3 color, glm::vec2 vertices[4]) const +{ + return render_rect(glm::vec4(color, 1.0f), vertices); +} + +//--------------------------------------------------------------------------- +// channelsettings::fftcontrol::render_rect (private) +// +// Renders a line primitive +// +// Arguments: +// +// color - Color value to use when rendering +// vertices - Rectangle vertices + +void channelsettings::fftcontrol::render_rect(glm::vec4 color, glm::vec2 vertices[4]) const +{ + // Set the specific color value before drawing the line + glUniform4f(m_shader.uColor(), color.r, color.g, color.b, color.a); + + // Render the rectangle as a 4-vertex GL_TRIANGLE_STRIP primitive + glBufferData(GL_ARRAY_BUFFER, (sizeof(glm::vec2) * 4), &vertices[0], GL_STATIC_DRAW); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); +} + +//--------------------------------------------------------------------------- +// channelsettings::fftcontrol::render_triangle (private) +// +// Renders a line primitive +// +// Arguments: +// +// color - Color value to use when rendering +// vertices - Triangle vertices + +void channelsettings::fftcontrol::render_triangle(glm::vec3 color, glm::vec2 vertices[3]) const +{ + return render_triangle(glm::vec4(color, 1.0f), vertices); +} + +//--------------------------------------------------------------------------- +// channelsettings::fftcontrol::render_triangle (private) +// +// Renders a line primitive +// +// Arguments: +// +// color - Color value to use when rendering +// vertices - Triangle vertices + +void channelsettings::fftcontrol::render_triangle(glm::vec4 color, glm::vec2 vertices[3]) const +{ + // Set the specific color value before drawing the line + glUniform4f(m_shader.uColor(), color.r, color.g, color.b, color.a); + + // Render the triangle as a 3-vertex GL_TRIANGLES primitive + glBufferData(GL_ARRAY_BUFFER, (sizeof(glm::vec2) * 3), &vertices[0], GL_STATIC_DRAW); + glDrawArrays(GL_TRIANGLES, 0, 3); +} + +//--------------------------------------------------------------------------- +// channelsettings::fftcontrol::update +// +// Updates the rendering control state and flags it as dirty +// +// Arguments: +// +// status - Signal meter status +// signallock - Flag indicating a signal lock +// muxlock - Flag indicating a multiplex lock + +void channelsettings::fftcontrol::update(struct signalmeter::signal_status const& status, + bool signallock, + bool muxlock) +{ + // Power and noise values are supplied as dB and need to be scaled to the viewport + m_power = db_to_height(status.power); + m_noise = db_to_height(status.noise); + + // The low and high cuts are provided as indexes into the plot data + m_fftlowcut = status.lowcut; + m_ffthighcut = status.highcut; + + // The length of the fft data should match the width of the control, but watch for overrruns + assert(status.plotsize == m_width); + size_t length = std::min(status.plotsize, static_cast(m_width)); + + // The FFT data merely needs to be converted into an X,Y vertex to be used by the renderer + for (size_t index = 0; index < length; index++) + m_fft[index] = glm::vec2(static_cast(index), static_cast(status.plotdata[index])); + + // The FFT line strip will be shown in a different color based on this information + m_overload = status.overload; + m_signallock = signallock; + m_muxlock = muxlock; + + // In the event of an FFT data underrun, flat-line the remainder of the data points + if (m_width > status.plotsize) + { + + for (size_t index = length; index < m_width; index++) + m_fft[index] = glm::vec2(static_cast(index), m_heightf); + } + + m_dirty = true; // Scene needs to be re-rendered +} + +//--------------------------------------------------------------------------- +// channelsettings::fftcontrol::width +// +// Retrieves the width of the control +// +// Arguments: +// +// NONE + +size_t channelsettings::fftcontrol::width(void) const +{ + return static_cast(m_width); +} + +//--------------------------------------------------------------------------- +// channelsettings::fftshader Constructor +// +// Arguments: +// +// NONE + +channelsettings::fftshader::fftshader() +{ + // VERTEX SHADER + std::string const vertexshader( + R"( +uniform mat4 u_modelViewProjectionMatrix; + +#ifdef GL_ES +attribute vec2 a_position; +#else +in vec2 a_position; +#endif + +void main() +{ + gl_Position = u_modelViewProjectionMatrix * vec4(a_position, 0.0, 1.0); +} + )"); + + // FRAGMENT SHADER + std::string const fragmentshader( + R"( +#ifdef GL_ES +precision mediump float; +#else +precision highp float; +#endif + +uniform vec4 u_color; + +#ifndef GL_ES +out vec4 FragColor; +#endif + +void main() +{ +#ifdef GL_ES + gl_FragColor = u_color; +#else + FragColor = u_color; +#endif +} + )"); + + // Compile and link the shader programs during construction + if (is_platform_opengles()) + CompileAndLink("#version 100\n", vertexshader, "#version 100\n", fragmentshader); + else + CompileAndLink("#version 150\n", vertexshader, "#version 150\n", fragmentshader); +} + +//--------------------------------------------------------------------------- +// channelsettings::fftshader::aPosition (const) +// +// Gets the location of the aPosition shader variable +// +// Arguments: +// +// NONE + +GLint channelsettings::fftshader::aPosition(void) const +{ + assert(m_aPosition != -1); + return m_aPosition; +} + +//--------------------------------------------------------------------------- +// channelsettings::fftshader::OnCompiledAndLinked (CShaderProgram) +// +// Invoked when the shader has been compiled and linked +// +// Arguments: +// +// NONE + +void channelsettings::fftshader::OnCompiledAndLinked(void) +{ + m_aPosition = glGetAttribLocation(ProgramHandle(), "a_position"); + m_uColor = glGetUniformLocation(ProgramHandle(), "u_color"); + m_uModelProjMatrix = glGetUniformLocation(ProgramHandle(), "u_modelViewProjectionMatrix"); +} + +//--------------------------------------------------------------------------- +// channelsettings::fftshader::OnDisabled (CShaderProgram) +// +// Invoked when the shader has been disabled +// +// Arguments: +// +// NONE + +void channelsettings::fftshader::OnDisabled(void) +{ +} + +//--------------------------------------------------------------------------- +// channelsettings::fftshader::OnEnabled (CShaderProgram) +// +// Invoked when the shader has been enabled +// +// Arguments: +// +// NONE + +bool channelsettings::fftshader::OnEnabled(void) +{ + return true; +} + +//--------------------------------------------------------------------------- +// channelsettings::fftshader::uColor (const) +// +// Gets the location of the uColor shader variable +// +// Arguments: +// +// NONE + +GLint channelsettings::fftshader::uColor(void) const +{ + assert(m_uColor != -1); + return m_uColor; +} + +//--------------------------------------------------------------------------- +// channelsettings::fftshader::uModelProjMatrix (const) +// +// Gets the location of the aPosition shader variable +// +// Arguments: +// +// NONE + +GLint channelsettings::fftshader::uModelProjMatrix(void) const +{ + assert(m_uModelProjMatrix != -1); + return m_uModelProjMatrix; +} + +//--------------------------------------------------------------------------- +// channelsettings Constructor (private) +// +// Arguments: +// +// device - Device instance +// tunerprops - Tuner properties +// channelprops - Channel properties + +channelsettings::channelsettings(std::unique_ptr device, + struct tunerprops const& tunerprops, + struct channelprops const& channelprops, + bool isnew) + : kodi::gui::CWindow("channelsettings.xml", "skin.estuary", true), + m_device(std::move(device)), + m_tunerprops(tunerprops), + m_channelprops(channelprops), + m_isnew(isnew) +{ + assert(m_device); + + m_signalprops.filter = false; // Never apply the filter here + + // Analog FM + // + if (channelprops.modulation == modulation::fm) + { + + m_signalprops.samplerate = 1600 KHz; + m_signalprops.bandwidth = 220 KHz; + m_signalprops.lowcut = -103 KHz; + m_signalprops.highcut = 103 KHz; + + // Analog signals require a DC offset to be applied to prevent a natural + // spike from occurring at the center frequency on many RTL-SDR devices + m_signalprops.offset = (m_signalprops.samplerate / 4); + } + + // HD Radio + // + else if (channelprops.modulation == modulation::hd) + { + + m_signalprops.samplerate = 1488375; + m_signalprops.bandwidth = 440 KHz; + m_signalprops.lowcut = -204 KHz; + m_signalprops.highcut = 204 KHz; + m_signalprops.offset = 0; + } + + // DAB Ensemble + // + else if (channelprops.modulation == modulation::dab) + { + + m_signalprops.samplerate = 2048 KHz; + m_signalprops.bandwidth = 1712 KHz; + m_signalprops.lowcut = -780 KHz; + m_signalprops.highcut = 780 KHz; + m_signalprops.offset = 0; + } + + // Weather Radio + // + else if (channelprops.modulation == modulation::wx) + { + + m_signalprops.samplerate = 1600 KHz; + m_signalprops.bandwidth = 200 KHz; + m_signalprops.lowcut = -8 KHz; + m_signalprops.highcut = 8 KHz; + + // Analog signals require a DC offset to be applied to prevent a natural + // spike from occurring at the center frequency on many RTL-SDR devices + m_signalprops.offset = (m_signalprops.samplerate / 4); + } + + else + throw string_exception("unknown channel modulation"); + + // Get the valid manual gain values supported by the device + m_device->get_valid_gains(m_manualgains); + + // Set the device to match the channel properties at time of construction (prior to OnCreate) + m_device->set_center_frequency(m_channelprops.frequency + m_signalprops.offset); + m_device->set_frequency_correction(m_tunerprops.freqcorrection + m_channelprops.freqcorrection); + m_device->set_sample_rate(m_signalprops.samplerate); + m_device->set_automatic_gain_control(m_channelprops.autogain); + if (!m_channelprops.autogain) + m_device->set_gain(m_channelprops.manualgain); +} + +//--------------------------------------------------------------------------- +// channelsettings Destructor + +channelsettings::~channelsettings() +{ + m_stop = true; // Signal worker thread to stop + if (m_device) + m_device->cancel_async(); // Cancel any async read operations + if (m_worker.joinable()) + m_worker.join(); // Wait for thread to exit + m_device.reset(); // Release RTL-SDR device +} + +//--------------------------------------------------------------------------- +// channelsettings::close +// +// Closes the dialog box +// +// Arguments: +// +// result - Result value from the dialog box operation + +void channelsettings::close(bool result) +{ + m_result = result; // Set the result code + Close(); // Close the dialog box +} + +//--------------------------------------------------------------------------- +// channelsettings::create (static) +// +// Factory method, creates a new channelsettings instance +// +// Arguments: +// +// device - Device instance +// tunerprops - Tuner properties +// channelprops - Channel properties + +std::unique_ptr channelsettings::create(std::unique_ptr device, + struct tunerprops const& tunerprops, + struct channelprops const& channelprops) +{ + return create(std::move(device), tunerprops, channelprops, false); +} + +//--------------------------------------------------------------------------- +// channelsettings::create (static) +// +// Factory method, creates a new channelsettings instance +// +// Arguments: +// +// device - Device instance +// tunerprops - Tuner properties +// channelprops - Channel properties +// isnew - Flag indicating if this is a new channel + +std::unique_ptr channelsettings::create(std::unique_ptr device, + struct tunerprops const& tunerprops, + struct channelprops const& channelprops, + bool isnew) +{ + return std::unique_ptr( + new channelsettings(std::move(device), tunerprops, channelprops, isnew)); +} + +//--------------------------------------------------------------------------- +// channelsettings::gain_to_percent (private) +// +// Converts a manual gain value into a percentage +// +// Arguments: +// +// gain - Gain to convert + +int channelsettings::gain_to_percent(int gain) const +{ + if (m_manualgains.empty()) + return 0; + + // Convert the gain into something that's valid for the tuner + gain = nearest_valid_gain(gain); + + // Use the index within the gain table to generate the percentage + for (size_t index = 0; index < m_manualgains.size(); index++) + { + + if (gain == m_manualgains[index]) + return static_cast((index * 100) / (m_manualgains.size() - 1)); + } + + return 0; +} + +//--------------------------------------------------------------------------- +// channelsettings::get_channel_properties +// +// Gets the updated channel properties from the dialog box +// +// Arguments: +// +// channelprops - Structure to receive the updated channel properties + +void channelsettings::get_channel_properties(struct channelprops& channelprops) const +{ + channelprops = m_channelprops; +} + +//--------------------------------------------------------------------------- +// channelsettings::get_dialog_result +// +// Gets the result code from the dialog box +// +// Arguments: +// +// NONE + +bool channelsettings::get_dialog_result(void) const +{ + return m_result; +} + +//--------------------------------------------------------------------------- +// channelsettings::get_subchannel_properties +// +// Gets the updated subchannel properties from the dialog box +// +// Arguments: +// +// subchannelprops - vector<> to receive the subchannel properties + +void channelsettings::get_subchannel_properties( + std::vector& subchannelprops) const +{ + subchannelprops.clear(); // Ensure the vector<> is empty + + // Copy the known subchannel information into the output vector<> + for (auto const& it : m_muxdata.subchannels) + subchannelprops.push_back({it.number, it.name}); + + // Sort the vector<> by the subchannel number before returning + std::sort(subchannelprops.begin(), subchannelprops.end(), + [](auto const& lhs, auto const& rhs) -> bool { return lhs.number < rhs.number; }); +} + +//--------------------------------------------------------------------------- +// channelsettings::meter_status (private) +// +// Updates the state of the signal meter +// +// Arguments: +// +// status - Updated signal status from the signal meter + +void channelsettings::meter_status(struct signalmeter::signal_status const& status) +{ + std::unique_lock lock(m_muxdatalock); // Synchronization object + bool signallock = true; // Signal lock + bool muxlock = false; // Multiplex lock + char strbuf[64] = {}; // snprintf() text buffer + + // For digital signals we can determine signal lock and multiplex lock values + if (m_muxscanner) + { + + signallock = m_muxdata.sync; + if (signallock) + { + + // A multiplex lock occurs when a name has been assigned to the multiplex + // as well as all of the detected subchannels within that multiplex + if ((!m_muxdata.name.empty()) && (m_muxdata.subchannels.size() > 0)) + { + + muxlock = true; + for (auto const& iterator : m_muxdata.subchannels) + if (iterator.name.empty()) + { + muxlock = false; + break; + } + } + } + } + + lock.unlock(); // Release multiplex data lock + + // Signal Meter + // + m_render_signalmeter->update(status, signallock, muxlock); + + // Signal Strength + // + if (!std::isnan(status.power)) + { + + snprintf(strbuf, std::extent::value, "%.1F dB", status.power); + m_edit_signalpower->SetText(strbuf); + } + else + m_edit_signalpower->SetText("N/A"); + + // Signal-to-noise + // + if (!std::isnan(status.snr)) + { + + snprintf(strbuf, std::extent::value, "%d dB", static_cast(status.snr)); + m_edit_signalsnr->SetText(strbuf); + } + else + m_edit_signalsnr->SetText("N/A"); +} + +//--------------------------------------------------------------------------- +// channelsettings::mux_data (private) +// +// Updates the state of the multiplex data +// +// Arguments: +// +// muxdata - Updated multiplex data from the mux scanner + +void channelsettings::mux_data(struct muxscanner::multiplex const& muxdata) +{ + std::unique_lock lock(m_muxdatalock); + + m_muxdata = muxdata; // Copy the updated mutex information + + // Change the channel name automatically if the channel is new + if (m_isnew && !m_muxdata.name.empty()) + { + + m_channelprops.name = m_muxdata.name; + m_edit_channelname->SetText(m_channelprops.name); + } +} + +//--------------------------------------------------------------------------- +// channelsettings::nearest_valid_gain (private) +// +// Gets the closest valid value for a manual gain setting +// +// Arguments: +// +// gain - Gain to adjust + +int channelsettings::nearest_valid_gain(int gain) const +{ + if (m_manualgains.empty()) + return 0; + + // Select the gain value that's closest to what has been requested + int nearest = m_manualgains[0]; + for (size_t index = 0; index < m_manualgains.size(); index++) + { + + if (std::abs(gain - m_manualgains[index]) < std::abs(gain - nearest)) + nearest = m_manualgains[index]; + } + + return nearest; +} + +//--------------------------------------------------------------------------- +// channelsettings::percent_to_gain (private) +// +// Converts a percentage into a manual gain value +// +// Arguments: +// +// percent - Percentage to convert + +int channelsettings::percent_to_gain(int percent) const +{ + if (m_manualgains.empty()) + return 0; + + if (percent == 0) + return m_manualgains.front(); + else if (percent == 100) + return m_manualgains.back(); + + return m_manualgains[(percent * m_manualgains.size()) / 100]; +} + +//--------------------------------------------------------------------------- +// channelsettings::update_gain (private) +// +// Updates the state of the gain control +// +// Arguments: +// +// NONE + +void channelsettings::update_gain(void) +{ + char strbuf[64] = {}; // snprintf() text buffer + + if (!m_channelprops.autogain) + { + + // Convert the gain value from tenths of a decibel into XX.X dB format + snprintf(strbuf, std::extent::value, "%.1f dB", + m_channelprops.manualgain / 10.0); + m_edit_signalgain->SetText(strbuf); + } + + else + m_edit_signalgain->SetText("Auto"); +} + +//--------------------------------------------------------------------------- +// channelsettings::worker (private) +// +// Worker thread procedure used to pump data into the signal meter +// +// Arguments: +// +// started - Condition variable to set when thread has started + +void channelsettings::worker(scalar_condition& started) +{ + assert(m_device); + assert(m_signalmeter); + + // read_callback_func (local) + // + // Asynchronous read callback function for the RTL-SDR device + auto read_callback_func = [&](uint8_t const* buffer, size_t count) -> void + { + // Feed the signal meter, and optionally the multiplex scanner + m_signalmeter->inputsamples(buffer, count); + if (m_muxscanner) + m_muxscanner->inputsamples(buffer, count); + }; + + // Begin streaming from the device and inform the caller that the thread is running + m_device->begin_stream(); + started = true; + + // Continuously read data from the device until cancel_async() has been called + try + { + m_device->read_async(read_callback_func, static_cast(32 KiB)); + } + catch (...) + { + m_worker_exception = std::current_exception(); + } + + m_stopped.store(true); // Thread is stopped +} + +//--------------------------------------------------------------------------- +// CWINDOW IMPLEMENTATION +//--------------------------------------------------------------------------- + +//--------------------------------------------------------------------------- +// channelsettings::OnAction (CWindow) +// +// Receives action codes that are sent to this window +// +// Arguments: +// +// actionId - The action id to perform + +bool channelsettings::OnAction(ADDON_ACTION actionId) +{ + return kodi::gui::CWindow::OnAction(actionId); +} + +//--------------------------------------------------------------------------- +// channelsettings::OnClick (CWindow) +// +// Receives click event notifications for a control +// +// controlId - GUI control identifier + +bool channelsettings::OnClick(int controlId) +{ + uint32_t browseheading = 30312; // "Browse for channel logo" + + // "Browse for multiplex logo" / "Browse for emsemble logo" + if (m_channelprops.modulation == modulation::hd) + browseheading = 30313; + else if (m_channelprops.modulation == modulation::dab) + browseheading = 30314; + + switch (controlId) + { + + case CONTROL_EDIT_CHANNELNAME: + m_channelprops.name = m_edit_channelname->GetText(); + break; + + case CONTROL_BUTTON_CHANNELICON: + kodi::gui::dialogs::FileBrowser::ShowAndGetImage( + "local|network|pictures", kodi::addon::GetLocalizedString(browseheading), + m_channelprops.logourl); + m_image_channelicon->SetFileName(m_channelprops.logourl, false); + return true; + + case CONTROL_RADIO_AUTOMATICGAIN: + m_channelprops.autogain = m_radio_autogain->IsSelected(); + m_device->set_automatic_gain_control(m_channelprops.autogain); + if (!m_channelprops.autogain) + m_device->set_gain(m_channelprops.manualgain); + m_slider_manualgain->SetEnabled(!m_channelprops.autogain); + update_gain(); + return true; + + case CONTROL_SLIDER_MANUALGAIN: + m_channelprops.manualgain = + percent_to_gain(static_cast(m_slider_manualgain->GetPercentage())); + if (!m_channelprops.autogain) + m_device->set_gain(m_channelprops.manualgain); + update_gain(); + return true; + + case CONTROL_SLIDER_CORRECTION: + m_channelprops.freqcorrection = m_slider_correction->GetIntValue(); + m_device->set_frequency_correction(m_tunerprops.freqcorrection + + m_channelprops.freqcorrection); + return true; + + case CONTROL_BUTTON_OK: + close(true); + return true; + + case CONTROL_BUTTON_CANCEL: + close(false); + return true; + } + + return kodi::gui::CWindow::OnClick(controlId); +} + +//--------------------------------------------------------------------------- +// channelsettings::OnInit (CWindow) +// +// Called to initialize the window object +// +// Arguments: +// +// NONE + +bool channelsettings::OnInit(void) +{ + assert(m_device); + + try + { + + // Get references to all of the manipulable dialog controls + m_button_ok = std::unique_ptr(new CButton(this, CONTROL_BUTTON_OK)); + m_edit_frequency = std::unique_ptr(new CEdit(this, CONTROL_EDIT_FREQUENCY)); + m_edit_channelname = std::unique_ptr(new CEdit(this, CONTROL_EDIT_CHANNELNAME)); + m_edit_modulation = std::unique_ptr(new CEdit(this, CONTROL_EDIT_MODULATION)); + m_button_channelicon = std::unique_ptr(new CButton(this, CONTROL_BUTTON_CHANNELICON)); + m_image_channelicon = std::unique_ptr(new CImage(this, CONTROL_IMAGE_CHANNELICON)); + m_radio_autogain = + std::unique_ptr(new CRadioButton(this, CONTROL_RADIO_AUTOMATICGAIN)); + m_slider_manualgain = + std::unique_ptr(new CSettingsSlider(this, CONTROL_SLIDER_MANUALGAIN)); + m_slider_correction = + std::unique_ptr(new CSettingsSlider(this, CONTROL_SLIDER_CORRECTION)); + m_render_signalmeter = + std::unique_ptr(new fftcontrol(this, CONTROL_RENDER_SIGNALMETER)); + m_edit_signalgain = std::unique_ptr(new CEdit(this, CONTROL_EDIT_METERGAIN)); + m_edit_signalpower = std::unique_ptr(new CEdit(this, CONTROL_EDIT_METERPOWER)); + m_edit_signalsnr = std::unique_ptr(new CEdit(this, CONTROL_EDIT_METERSNR)); + + // Set the window title based on if this is a single channel or a multiplex/ensemble + std::unique_ptr headerlabel(new CLabel(this, CONTROL_LABEL_HEADERLABEL)); + headerlabel->SetLabel(kodi::addon::GetLocalizedString(30301)); + if (m_channelprops.modulation == modulation::hd) + headerlabel->SetLabel(kodi::addon::GetLocalizedString(30302)); + else if (m_channelprops.modulation == modulation::dab) + headerlabel->SetLabel(kodi::addon::GetLocalizedString(30303)); + + // Change the text of the OK button to "Add" if we are in new channel mode + if (m_isnew) + m_button_ok->SetLabel(kodi::addon::GetLocalizedString(15019)); + + // Set the channel frequency in XXX.X MHz (FM/HD) or XXX.XXX format (DAB/WX) + char freqstr[128]; + double frequency = (m_channelprops.frequency / static_cast(100000)) / 10.0; + if ((m_channelprops.modulation == modulation::dab) || + (m_channelprops.modulation == modulation::wx)) + snprintf(freqstr, std::extent::value, "%.3f MHz", frequency); + else + snprintf(freqstr, std::extent::value, "%.1f MHz", frequency); + m_edit_frequency->SetText(freqstr); + + // Set the channel name and logo/icon + m_edit_channelname->SetText(m_channelprops.name); + m_image_channelicon->SetFileName(m_channelprops.logourl, false); + + // Channel name is disabled for multiplex/ensemble modulations + if ((m_channelprops.modulation == modulation::hd) || + (m_channelprops.modulation == modulation::dab)) + m_edit_channelname->SetEnabled(false); + + // Set the modulation type + if (m_channelprops.modulation == modulation::fm) + m_edit_modulation->SetText(kodi::addon::GetLocalizedString(30304)); + else if (m_channelprops.modulation == modulation::hd) + m_edit_modulation->SetText(kodi::addon::GetLocalizedString(30305)); + else if (m_channelprops.modulation == modulation::dab) + m_edit_modulation->SetText(kodi::addon::GetLocalizedString(30306)); + else if (m_channelprops.modulation == modulation::wx) + m_edit_modulation->SetText(kodi::addon::GetLocalizedString(30307)); + + // Change the text of the Name edit for multiplex/ensemble channels (HD/DAB) + if (m_channelprops.modulation == modulation::hd) + m_edit_channelname->SetLabel(kodi::addon::GetLocalizedString(30308)); + else if (m_channelprops.modulation == modulation::dab) + m_edit_channelname->SetLabel(kodi::addon::GetLocalizedString(30309)); + + // Change the text of the Logo button for multiplex/ensemble channels (HD/DAB) + if (m_channelprops.modulation == modulation::hd) + m_button_channelicon->SetLabel(kodi::addon::GetLocalizedString(30310)); + else if (m_channelprops.modulation == modulation::dab) + m_button_channelicon->SetLabel(kodi::addon::GetLocalizedString(30311)); + + // Adjust the manual gain value to match something that the tuner supports + m_channelprops.manualgain = nearest_valid_gain(m_channelprops.manualgain); + + // Set the tuner gain parameters + m_radio_autogain->SetSelected(m_channelprops.autogain); + m_slider_manualgain->SetEnabled(!m_channelprops.autogain); + m_slider_manualgain->SetPercentage( + static_cast(gain_to_percent(m_channelprops.manualgain))); + update_gain(); + + // Set the frequency correction parameters + m_slider_correction->SetIntInterval(1); + m_slider_correction->SetIntRange(-41, 40); + m_slider_correction->SetIntValue(m_channelprops.freqcorrection); + + // Set the default text for the signal indicators + m_edit_signalpower->SetText("N/A"); + m_edit_signalsnr->SetText("N/A"); + + // Initialize the signal meter plot properties based on the size of the render control + struct signalplotprops plotprops = {}; + plotprops.height = m_render_signalmeter->height(); + plotprops.width = m_render_signalmeter->width(); + plotprops.mindb = FFT_MINDB; + plotprops.maxdb = FFT_MAXDB; + + // Create the signal meter instance with a 100ms callback rate + m_signalmeter = + signalmeter::create(m_signalprops, plotprops, 100, + std::bind(&channelsettings::meter_status, this, std::placeholders::_1)); + + // Create the multiplex scanner instance if applicable to the modulation + if (m_channelprops.modulation == modulation::hd) + m_muxscanner = + hdmuxscanner::create(m_signalprops.samplerate, m_channelprops.frequency, + std::bind(&channelsettings::mux_data, this, std::placeholders::_1)); + else if (m_channelprops.modulation == modulation::dab) + m_muxscanner = + dabmuxscanner::create(m_signalprops.samplerate, + std::bind(&channelsettings::mux_data, this, std::placeholders::_1)); + + // Create a worker thread on which to pump data into the signal meter + scalar_condition started{false}; + m_worker = std::thread(&channelsettings::worker, this, std::ref(started)); + started.wait_until_equals(true); + } + + catch (...) + { + return false; + } + + return kodi::gui::CWindow::OnInit(); +} + +//--------------------------------------------------------------------------- + +#pragma warning(pop) diff --git a/src/channelsettings.h b/src/channelsettings.h index ba6ce66..671779d 100644 --- a/src/channelsettings.h +++ b/src/channelsettings.h @@ -1,319 +1,319 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//----------------------------------------------------------------------------- - -#ifndef __CHANNELSETTINGS_H_ -#define __CHANNELSETTINGS_H_ -#pragma once - -#include "muxscanner.h" -#include "props.h" -#include "renderingcontrol.h" -#include "rtldevice.h" -#include "scalar_condition.h" -#include "signalmeter.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#pragma warning(push, 4) - -using namespace kodi::gui::controls; - -//--------------------------------------------------------------------------- -// Class channelsettings -// -// Implements the "Channel Settings" dialog - -class ATTR_DLL_LOCAL channelsettings : public kodi::gui::CWindow -{ -public: - // Destructor - // - ~channelsettings() override; - - //----------------------------------------------------------------------- - // Member Functions - - // create (static) - // - // Factory method, creates a new channelsettings instance - static std::unique_ptr create(std::unique_ptr device, - struct tunerprops const& tunerprops, - struct channelprops const& channelprops); - static std::unique_ptr create(std::unique_ptr device, - struct tunerprops const& tunerprops, - struct channelprops const& channelprops, - bool isnew); - - // get_channel_properties - // - // Gets the updated channel properties from the dialog box - void get_channel_properties(struct channelprops& channelprops) const; - - // get_dialog_result - // - // Gets the result code from the dialog box - bool get_dialog_result(void) const; - - // get_subchannel_properties - // - // Gets the updated subchannel properties from the dialog box - void get_subchannel_properties(std::vector& subchannelprops) const; - -private: - channelsettings(channelsettings const&) = delete; - channelsettings& operator=(channelsettings const&) = delete; - - // FFT_BANDWIDTH - // - // Bandwidth of the FFT display - static uint32_t const FFT_BANDWIDTH; - - // FFT_MAXDB - // - // Maximum decibel value supported by the FFT - static float const FFT_MAXDB; - - // FFT_MINDB - // - // Minimum decibel level supported by the FFT - static float const FFT_MINDB; - - // Instance Constructor - // - channelsettings(std::unique_ptr device, - struct tunerprops const& tunerprops, - struct channelprops const& channelprops, - bool isnew); - - //----------------------------------------------------------------------- - // Private Type Declarations - - // Class fftshader - // - // Implements the shader for the FFT rendering control - class fftshader : public kodi::gui::gl::CShaderProgram - { - public: - // Constructor - // - fftshader(); - - // Member Functions - // - GLint aPosition(void) const; - GLint uColor() const; - GLint uModelProjMatrix(void) const; - - private: - fftshader(fftshader const&) = delete; - fftshader& operator=(fftshader const&) = delete; - - // CShaderProgram overrides - // - void OnCompiledAndLinked(void) override; - void OnDisabled(void) override; - bool OnEnabled(void) override; - - GLint m_aPosition = -1; // Attribute location - GLint m_uColor = -1; // Attribute location - GLint m_uModelProjMatrix = -1; // Uniform location - }; - - // Class fftcontrol - // - // Implements the FFT rendering control - class fftcontrol : public renderingcontrol - { - public: - // Constructor / Destructor - // - fftcontrol(kodi::gui::CWindow* window, int controlid); - ~fftcontrol() override; - - // Member Functions - // - size_t height(void) const; - size_t width(void) const; - void update(struct signalmeter::signal_status const& status, - bool signallock, - bool muxlockupdate); - - private: - fftcontrol(fftcontrol const&) = delete; - fftcontrol& operator=(fftcontrol const&) = delete; - - // Private Member Functions - // - GLfloat db_to_height(float db) const; - void render_line(glm::vec3 color, glm::vec2 vertices[2]) const; - void render_line(glm::vec4 color, glm::vec2 vertices[2]) const; - void render_line_strip(glm::vec3 color, glm::vec2 vertices[], size_t numvertices) const; - void render_line_strip(glm::vec4 color, glm::vec2 vertices[], size_t numvertices) const; - void render_rect(glm::vec3 color, glm::vec2 vertices[4]) const; - void render_rect(glm::vec4 color, glm::vec2 vertices[4]) const; - void render_triangle(glm::vec3 color, glm::vec2 vertices[3]) const; - void render_triangle(glm::vec4 color, glm::vec2 vertices[3]) const; - - // renderingcontrol overrides - // - bool dirty(void) override; - void render(void) override; - - GLfloat m_widthf; // Width as GLfloat - GLfloat m_heightf; // Height as GLfloat - GLfloat m_linewidthf = 1.25f; // Line width factor - GLfloat m_lineheightf = 1.25f; // Line height factor - - fftshader m_shader; // Shader instance - GLuint m_vertexVBO; // Vertex buffer object - glm::mat4 m_modelProjMat; // Model/Projection matrix - - bool m_dirty = false; // Dirty flag - GLfloat m_power = 0.0f; // Power level - GLfloat m_noise = 0.0f; // Noise level - bool m_overload = false; // Overload flag - bool m_signallock = false; // Signal lock flag - bool m_muxlock = false; // Multiplex lock flag - - std::unique_ptr m_fft; // FFT vertices - int m_fftlowcut; // FFT low cut index - int m_ffthighcut; // FFT high cut index - }; - - //------------------------------------------------------------------------- - // CWindow Implementation - - // OnAction - // - // Receives action codes that are sent to this window - bool OnAction(ADDON_ACTION actionId) override; - - // OnClick - // - // Receives click event notifications for a control - bool OnClick(int controlId) override; - - // OnInit - // - // Called to initialize the window object - bool OnInit(void) override; - - //------------------------------------------------------------------------- - // Private Member Functions - - // close - // - // Closes the dialog box - void close(bool result); - - // gain_to_percent - // - // Converts a manual gain value into a percentage - int gain_to_percent(int gain) const; - - // meter_status - // - // Updates the state of the signal meter - void meter_status(struct signalmeter::signal_status const& status); - - // mux_data - // - // Updates the state of the multiplex information - void mux_data(struct muxscanner::multiplex const& muxdata); - - // nearest_valid_gain - // - // Gets the closest valid value for a manual gain setting - int nearest_valid_gain(int gain) const; - - // percent_to_gain - // - // Converts a percentage into a manual gain value - int percent_to_gain(int percent) const; - - // update_gain - // - // Updates the state of the gain control - void update_gain(void); - - // worker - // - // Worker thread procedure used to pump data into the signal meter - void worker(scalar_condition& started); - - //------------------------------------------------------------------------- - // Member Variables - - std::unique_ptr m_device; // Device instance - struct tunerprops m_tunerprops = {}; // Tuner properties - struct channelprops m_channelprops = {}; // Channel properties - struct signalprops m_signalprops = {}; // Signal properties - struct muxscanner::multiplex m_muxdata = {}; // Multiplex properties - mutable std::mutex m_muxdatalock; // Multiplex properties lock - bool m_isnew = false; // New channel flag - std::unique_ptr m_signalmeter; // Signal meter instance - std::unique_ptr m_muxscanner; // Multiplex scanner instance - std::vector m_manualgains; // Manual gain values - bool m_result = false; // Dialog result - - std::thread m_worker; // Worker thread - std::exception_ptr m_worker_exception; // Exception on worker thread - scalar_condition m_stop{false}; // Condition to stop data transfer - std::atomic m_stopped{false}; // Data transfer stopped flag - - // CONTROLS - // - std::unique_ptr m_button_ok; // OK button - std::unique_ptr m_edit_frequency; // Frequency - std::unique_ptr m_edit_channelname; // Channel name - std::unique_ptr m_edit_modulation; // Channel modulation - std::unique_ptr m_button_channelicon; // Channel icon - std::unique_ptr m_image_channelicon; // Channel icon - std::unique_ptr m_radio_autogain; // Automatic gain - std::unique_ptr m_slider_manualgain; // Manual gain - std::unique_ptr m_slider_correction; // Frequency correction - std::unique_ptr m_render_signalmeter; // Signal meter - std::unique_ptr m_edit_signalgain; // Active gain - std::unique_ptr m_edit_signalpower; // Active power - std::unique_ptr m_edit_signalsnr; // Active SNR -}; - -//----------------------------------------------------------------------------- - -#pragma warning(pop) - -#endif // __CHANNELSETTINGS_H_ +//----------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef __CHANNELSETTINGS_H_ +#define __CHANNELSETTINGS_H_ +#pragma once + +#include "muxscanner.h" +#include "props.h" +#include "renderingcontrol.h" +#include "rtldevice.h" +#include "scalar_condition.h" +#include "signalmeter.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#pragma warning(push, 4) + +using namespace kodi::gui::controls; + +//--------------------------------------------------------------------------- +// Class channelsettings +// +// Implements the "Channel Settings" dialog + +class ATTR_DLL_LOCAL channelsettings : public kodi::gui::CWindow +{ +public: + // Destructor + // + ~channelsettings() override; + + //----------------------------------------------------------------------- + // Member Functions + + // create (static) + // + // Factory method, creates a new channelsettings instance + static std::unique_ptr create(std::unique_ptr device, + struct tunerprops const& tunerprops, + struct channelprops const& channelprops); + static std::unique_ptr create(std::unique_ptr device, + struct tunerprops const& tunerprops, + struct channelprops const& channelprops, + bool isnew); + + // get_channel_properties + // + // Gets the updated channel properties from the dialog box + void get_channel_properties(struct channelprops& channelprops) const; + + // get_dialog_result + // + // Gets the result code from the dialog box + bool get_dialog_result(void) const; + + // get_subchannel_properties + // + // Gets the updated subchannel properties from the dialog box + void get_subchannel_properties(std::vector& subchannelprops) const; + +private: + channelsettings(channelsettings const&) = delete; + channelsettings& operator=(channelsettings const&) = delete; + + // FFT_BANDWIDTH + // + // Bandwidth of the FFT display + static uint32_t const FFT_BANDWIDTH; + + // FFT_MAXDB + // + // Maximum decibel value supported by the FFT + static float const FFT_MAXDB; + + // FFT_MINDB + // + // Minimum decibel level supported by the FFT + static float const FFT_MINDB; + + // Instance Constructor + // + channelsettings(std::unique_ptr device, + struct tunerprops const& tunerprops, + struct channelprops const& channelprops, + bool isnew); + + //----------------------------------------------------------------------- + // Private Type Declarations + + // Class fftshader + // + // Implements the shader for the FFT rendering control + class fftshader : public kodi::gui::gl::CShaderProgram + { + public: + // Constructor + // + fftshader(); + + // Member Functions + // + GLint aPosition(void) const; + GLint uColor() const; + GLint uModelProjMatrix(void) const; + + private: + fftshader(fftshader const&) = delete; + fftshader& operator=(fftshader const&) = delete; + + // CShaderProgram overrides + // + void OnCompiledAndLinked(void) override; + void OnDisabled(void) override; + bool OnEnabled(void) override; + + GLint m_aPosition = -1; // Attribute location + GLint m_uColor = -1; // Attribute location + GLint m_uModelProjMatrix = -1; // Uniform location + }; + + // Class fftcontrol + // + // Implements the FFT rendering control + class fftcontrol : public renderingcontrol + { + public: + // Constructor / Destructor + // + fftcontrol(kodi::gui::CWindow* window, int controlid); + ~fftcontrol() override; + + // Member Functions + // + size_t height(void) const; + size_t width(void) const; + void update(struct signalmeter::signal_status const& status, + bool signallock, + bool muxlockupdate); + + private: + fftcontrol(fftcontrol const&) = delete; + fftcontrol& operator=(fftcontrol const&) = delete; + + // Private Member Functions + // + GLfloat db_to_height(float db) const; + void render_line(glm::vec3 color, glm::vec2 vertices[2]) const; + void render_line(glm::vec4 color, glm::vec2 vertices[2]) const; + void render_line_strip(glm::vec3 color, glm::vec2 vertices[], size_t numvertices) const; + void render_line_strip(glm::vec4 color, glm::vec2 vertices[], size_t numvertices) const; + void render_rect(glm::vec3 color, glm::vec2 vertices[4]) const; + void render_rect(glm::vec4 color, glm::vec2 vertices[4]) const; + void render_triangle(glm::vec3 color, glm::vec2 vertices[3]) const; + void render_triangle(glm::vec4 color, glm::vec2 vertices[3]) const; + + // renderingcontrol overrides + // + bool dirty(void) override; + void render(void) override; + + GLfloat m_widthf; // Width as GLfloat + GLfloat m_heightf; // Height as GLfloat + GLfloat m_linewidthf = 1.25f; // Line width factor + GLfloat m_lineheightf = 1.25f; // Line height factor + + fftshader m_shader; // Shader instance + GLuint m_vertexVBO; // Vertex buffer object + glm::mat4 m_modelProjMat; // Model/Projection matrix + + bool m_dirty = false; // Dirty flag + GLfloat m_power = 0.0f; // Power level + GLfloat m_noise = 0.0f; // Noise level + bool m_overload = false; // Overload flag + bool m_signallock = false; // Signal lock flag + bool m_muxlock = false; // Multiplex lock flag + + std::unique_ptr m_fft; // FFT vertices + int m_fftlowcut; // FFT low cut index + int m_ffthighcut; // FFT high cut index + }; + + //------------------------------------------------------------------------- + // CWindow Implementation + + // OnAction + // + // Receives action codes that are sent to this window + bool OnAction(ADDON_ACTION actionId) override; + + // OnClick + // + // Receives click event notifications for a control + bool OnClick(int controlId) override; + + // OnInit + // + // Called to initialize the window object + bool OnInit(void) override; + + //------------------------------------------------------------------------- + // Private Member Functions + + // close + // + // Closes the dialog box + void close(bool result); + + // gain_to_percent + // + // Converts a manual gain value into a percentage + int gain_to_percent(int gain) const; + + // meter_status + // + // Updates the state of the signal meter + void meter_status(struct signalmeter::signal_status const& status); + + // mux_data + // + // Updates the state of the multiplex information + void mux_data(struct muxscanner::multiplex const& muxdata); + + // nearest_valid_gain + // + // Gets the closest valid value for a manual gain setting + int nearest_valid_gain(int gain) const; + + // percent_to_gain + // + // Converts a percentage into a manual gain value + int percent_to_gain(int percent) const; + + // update_gain + // + // Updates the state of the gain control + void update_gain(void); + + // worker + // + // Worker thread procedure used to pump data into the signal meter + void worker(scalar_condition& started); + + //------------------------------------------------------------------------- + // Member Variables + + std::unique_ptr m_device; // Device instance + struct tunerprops m_tunerprops = {}; // Tuner properties + struct channelprops m_channelprops = {}; // Channel properties + struct signalprops m_signalprops = {}; // Signal properties + struct muxscanner::multiplex m_muxdata = {}; // Multiplex properties + mutable std::mutex m_muxdatalock; // Multiplex properties lock + bool m_isnew = false; // New channel flag + std::unique_ptr m_signalmeter; // Signal meter instance + std::unique_ptr m_muxscanner; // Multiplex scanner instance + std::vector m_manualgains; // Manual gain values + bool m_result = false; // Dialog result + + std::thread m_worker; // Worker thread + std::exception_ptr m_worker_exception; // Exception on worker thread + scalar_condition m_stop{false}; // Condition to stop data transfer + std::atomic m_stopped{false}; // Data transfer stopped flag + + // CONTROLS + // + std::unique_ptr m_button_ok; // OK button + std::unique_ptr m_edit_frequency; // Frequency + std::unique_ptr m_edit_channelname; // Channel name + std::unique_ptr m_edit_modulation; // Channel modulation + std::unique_ptr m_button_channelicon; // Channel icon + std::unique_ptr m_image_channelicon; // Channel icon + std::unique_ptr m_radio_autogain; // Automatic gain + std::unique_ptr m_slider_manualgain; // Manual gain + std::unique_ptr m_slider_correction; // Frequency correction + std::unique_ptr m_render_signalmeter; // Signal meter + std::unique_ptr m_edit_signalgain; // Active gain + std::unique_ptr m_edit_signalpower; // Active power + std::unique_ptr m_edit_signalsnr; // Active SNR +}; + +//----------------------------------------------------------------------------- + +#pragma warning(pop) + +#endif // __CHANNELSETTINGS_H_ diff --git a/src/compat/bionic/complex.cpp b/src/compat/bionic/complex.cpp index c07047a..cb82af1 100644 --- a/src/compat/bionic/complex.cpp +++ b/src/compat/bionic/complex.cpp @@ -1,85 +1,85 @@ -//--------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//--------------------------------------------------------------------------- - -#if defined(__ANDROID__) && (__ANDROID_API__ < 23) - -#include -#include - -//--------------------------------------------------------------------------- -// ANDROID 21 C99 COMPLEX NUMBER HELPERS -// -// Android API 21 (5.0) does not fully support C99 complex number support and -// a number of helper routines are required for the NRSC5 HD Radio demodulator -// to build on that platform/version without modifications - -#ifdef __cplusplus -extern "C" { -#endif - - float cabsf(float _Complex __z) - { - float* c = reinterpret_cast(&__z); - std::complex cpp(c[0], c[1]); - return std::abs(cpp); - } - - float cargf(float _Complex __z) - { - float* c = reinterpret_cast(&__z); - std::complex cpp(c[0], c[1]); - return std::arg(cpp); - } - - float _Complex cexpf(float _Complex __z) - { - float* c = reinterpret_cast(&__z); - std::complex cpp(c[0], c[1]); - std::complex result(std::exp(cpp)); - return { result.real(), result.imag() }; - } - - float cimagf(float _Complex __z) - { - return reinterpret_cast(&__z)[1]; - } - - float _Complex conjf(float _Complex __z) - { - float* c = reinterpret_cast(&__z); - std::complex cpp(c[0], c[1]); - std::complex result(std::conj(cpp)); - return { result.real(), result.imag() }; - } - - float crealf(float _Complex __z) - { - return reinterpret_cast(&__z)[0]; - } - -#ifdef __cplusplus -} -#endif - -#endif // defined(__ANDROID__) && (__ANDROID_API__ < 23) - -//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//--------------------------------------------------------------------------- + +#if defined(__ANDROID__) && (__ANDROID_API__ < 23) + +#include +#include + +//--------------------------------------------------------------------------- +// ANDROID 21 C99 COMPLEX NUMBER HELPERS +// +// Android API 21 (5.0) does not fully support C99 complex number support and +// a number of helper routines are required for the NRSC5 HD Radio demodulator +// to build on that platform/version without modifications + +#ifdef __cplusplus +extern "C" { +#endif + + float cabsf(float _Complex __z) + { + float* c = reinterpret_cast(&__z); + std::complex cpp(c[0], c[1]); + return std::abs(cpp); + } + + float cargf(float _Complex __z) + { + float* c = reinterpret_cast(&__z); + std::complex cpp(c[0], c[1]); + return std::arg(cpp); + } + + float _Complex cexpf(float _Complex __z) + { + float* c = reinterpret_cast(&__z); + std::complex cpp(c[0], c[1]); + std::complex result(std::exp(cpp)); + return { result.real(), result.imag() }; + } + + float cimagf(float _Complex __z) + { + return reinterpret_cast(&__z)[1]; + } + + float _Complex conjf(float _Complex __z) + { + float* c = reinterpret_cast(&__z); + std::complex cpp(c[0], c[1]); + std::complex result(std::conj(cpp)); + return { result.real(), result.imag() }; + } + + float crealf(float _Complex __z) + { + return reinterpret_cast(&__z)[0]; + } + +#ifdef __cplusplus +} +#endif + +#endif // defined(__ANDROID__) && (__ANDROID_API__ < 23) + +//--------------------------------------------------------------------------- diff --git a/src/compat/faad2-hdc/win32_ver.h b/src/compat/faad2-hdc/win32_ver.h index 9f7bf53..167f4c2 100644 --- a/src/compat/faad2-hdc/win32_ver.h +++ b/src/compat/faad2-hdc/win32_ver.h @@ -1 +1 @@ -#define PACKAGE_VERSION "2.10.0" +#define PACKAGE_VERSION "2.10.0" diff --git a/src/compat/id/commit.h b/src/compat/id/commit.h index 1495bd8..4c89a65 100644 --- a/src/compat/id/commit.h +++ b/src/compat/id/commit.h @@ -1,14 +1,14 @@ -// -// Copyright (c) 2014 The ANGLE Project Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. -// -// commit.h: -// This is a default commit hash header, when git is not available. -// - -#define ANGLE_COMMIT_HASH "unknown hash" -#define ANGLE_COMMIT_HASH_SIZE 12 -#define ANGLE_COMMIT_DATE "unknown date" - -#define ANGLE_DISABLE_PROGRAM_BINARY_LOAD +// +// Copyright (c) 2014 The ANGLE Project Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// commit.h: +// This is a default commit hash header, when git is not available. +// + +#define ANGLE_COMMIT_HASH "unknown hash" +#define ANGLE_COMMIT_HASH_SIZE 12 +#define ANGLE_COMMIT_DATE "unknown date" + +#define ANGLE_DISABLE_PROGRAM_BINARY_LOAD diff --git a/src/compat/libresample/config.h b/src/compat/libresample/config.h index c209647..fc8d675 100644 --- a/src/compat/libresample/config.h +++ b/src/compat/libresample/config.h @@ -1,8 +1,8 @@ -/* src/config.h. Generated from configtemplate.h by configure. */ -/* Run configure to generate config.h automatically on any - system supported by GNU autoconf. For all other systems, - use this file as a template to create config.h -*/ - -#define HAVE_INTTYPES_H 1 - +/* src/config.h. Generated from configtemplate.h by configure. */ +/* Run configure to generate config.h automatically on any + system supported by GNU autoconf. For all other systems, + use this file as a template to create config.h +*/ + +#define HAVE_INTTYPES_H 1 + diff --git a/src/compat/pthread.h b/src/compat/pthread.h index 5f50a6a..222c1a9 100644 --- a/src/compat/pthread.h +++ b/src/compat/pthread.h @@ -1,1605 +1,1605 @@ -/* - * Posix Threads library for Microsoft Windows - * - * Use at own risk, there is no implied warranty to this code. - * It uses undocumented features of Microsoft Windows that can change - * at any time in the future. - * - * (C) 2010 Lockless Inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * * Neither the name of Lockless Inc. nor the names of its contributors may be - * used to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AN - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, - * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE - * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - * OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/* - * You may want to use the MingW64 winpthreads library instead. - * It is based on this, but adds error checking. - */ - -/* - * Version 1.0.1 Released 2 Feb 2012 - * Fixes pthread_barrier_destroy() to wait for threads to exit the barrier. - */ - -#ifndef WIN_PTHREADS -#define WIN_PTHREADS - - -#include -#include -#include -#include -#include -#include - -#define PTHREAD_CANCEL_DISABLE 0 -#define PTHREAD_CANCEL_ENABLE 0x01 - -#define PTHREAD_CANCEL_DEFERRED 0 -#define PTHREAD_CANCEL_ASYNCHRONOUS 0x02 - -#define PTHREAD_CREATE_JOINABLE 0 -#define PTHREAD_CREATE_DETACHED 0x04 - -#define PTHREAD_EXPLICT_SCHED 0 -#define PTHREAD_INHERIT_SCHED 0x08 - -#define PTHREAD_SCOPE_PROCESS 0 -#define PTHREAD_SCOPE_SYSTEM 0x10 - -#define PTHREAD_DEFAULT_ATTR (PTHREAD_CANCEL_ENABLE) - -#define PTHREAD_CANCELED ((void *)((UINT_PTR)0xDEADBEEF)) - -#define PTHREAD_ONCE_INIT 0 -#define PTHREAD_MUTEX_INITIALIZER {(void*)-1,-1,0,0,0,0} -#define PTHREAD_RWLOCK_INITIALIZER {0} -#define PTHREAD_COND_INITIALIZER {0} -#define PTHREAD_BARRIER_INITIALIZER \ - {0,0,PTHREAD_MUTEX_INITIALIZER,PTHREAD_COND_INITIALIZER} -#define PTHREAD_SPINLOCK_INITIALIZER 0 - -#define PTHREAD_DESTRUCTOR_ITERATIONS 256 -#define PTHREAD_KEYS_MAX (1<<20) - -#define PTHREAD_MUTEX_NORMAL 0 -#define PTHREAD_MUTEX_ERRORCHECK 1 -#define PTHREAD_MUTEX_RECURSIVE 2 -#define PTHREAD_MUTEX_DEFAULT 3 -#define PTHREAD_MUTEX_SHARED 4 -#define PTHREAD_MUTEX_PRIVATE 0 -#define PTHREAD_PRIO_NONE 0 -#define PTHREAD_PRIO_INHERIT 8 -#define PTHREAD_PRIO_PROTECT 16 -#define PTHREAD_PRIO_MULT 32 -#define PTHREAD_PROCESS_SHARED 0 -#define PTHREAD_PROCESS_PRIVATE 1 - -#define PTHREAD_BARRIER_SERIAL_THREAD 1 - - -///* Windows doesn't have this, so declare it ourselves. */ -//struct timespec -//{ -// /* long long in windows is the same as long in unix for 64bit */ -// long long tv_sec; -// long long tv_nsec; -//}; - -typedef struct _pthread_cleanup _pthread_cleanup; -struct _pthread_cleanup -{ - void (*func)(void *); - void *arg; - _pthread_cleanup *next; -}; - -struct _pthread_v -{ - void *ret_arg; - void *(* func)(void *); - _pthread_cleanup *clean; - HANDLE h; - int cancelled; - unsigned p_state; - unsigned int keymax; - void **keyval; - - jmp_buf jb; -}; - -typedef struct _pthread_v *pthread_t; - -typedef struct pthread_barrier_t pthread_barrier_t; -struct pthread_barrier_t -{ - int count; - int total; - CRITICAL_SECTION m; - CONDITION_VARIABLE cv; -}; - -typedef struct pthread_attr_t pthread_attr_t; -struct pthread_attr_t -{ - unsigned p_state; - void *stack; - size_t s_size; -}; - -typedef long pthread_once_t; -typedef unsigned pthread_mutexattr_t; -typedef SRWLOCK pthread_rwlock_t; -typedef CRITICAL_SECTION pthread_mutex_t; -typedef unsigned pthread_key_t; -typedef void *pthread_barrierattr_t; -typedef long pthread_spinlock_t; -typedef int pthread_condattr_t; -typedef CONDITION_VARIABLE pthread_cond_t; -typedef int pthread_rwlockattr_t; - -volatile long _pthread_cancelling; - -int _pthread_concur; - -/* Will default to zero as needed */ -pthread_once_t _pthread_tls_once; -DWORD _pthread_tls; - -/* Note initializer is zero, so this works */ -pthread_rwlock_t _pthread_key_lock; -unsigned int _pthread_key_max; -unsigned int _pthread_key_sch; -void (**_pthread_key_dest)(void *); - -static int pthread_rwlock_unlock(pthread_rwlock_t *l); - - -#define pthread_cleanup_push(F, A)\ -{\ - const _pthread_cleanup _pthread_cup = {(F), (A), pthread_self()->clean};\ - _ReadWriteBarrier();\ - pthread_self()->clean = (_pthread_cleanup *) &_pthread_cup;\ - _ReadWriteBarrier() - -/* Note that if async cancelling is used, then there is a race here */ -#define pthread_cleanup_pop(E)\ - (pthread_self()->clean = _pthread_cup.next, (E?_pthread_cup.func(_pthread_cup.arg):0));} - -static void _pthread_once_cleanup(void *o) -{ - *(pthread_once_t*)o = 0; -} - -static pthread_t pthread_self(void); -static int pthread_once(pthread_once_t *o, void (*func)(void)) -{ - long state = *o; - - _ReadWriteBarrier(); - - while (state != 1) - { - if (!state) - { - if (!_InterlockedCompareExchange(o, 2, 0)) - { - /* Success */ - pthread_cleanup_push(_pthread_once_cleanup, o); - func(); - pthread_cleanup_pop(0); - - /* Mark as done */ - *o = 1; - - return 0; - } - } - - YieldProcessor(); - - _ReadWriteBarrier(); - - state = *o; - } - - /* Done */ - return 0; -} - -static int _pthread_once_raw(pthread_once_t *o, void (*func)(void)) -{ - long state = *o; - - _ReadWriteBarrier(); - - while (state != 1) - { - if (!state) - { - if (!_InterlockedCompareExchange(o, 2, 0)) - { - /* Success */ - func(); - - /* Mark as done */ - *o = 1; - - return 0; - } - } - - YieldProcessor(); - - _ReadWriteBarrier(); - - state = *o; - } - - /* Done */ - return 0; -} - -static int pthread_mutex_lock(pthread_mutex_t *m) -{ - EnterCriticalSection(m); - return 0; -} - -static int pthread_mutex_unlock(pthread_mutex_t *m) -{ - LeaveCriticalSection(m); - return 0; -} - -static int pthread_mutex_trylock(pthread_mutex_t *m) -{ - return TryEnterCriticalSection(m) ? 0 : EBUSY; -} - -static int pthread_mutex_init(pthread_mutex_t *m, pthread_mutexattr_t *a) -{ - (void) a; - InitializeCriticalSection(m); - - return 0; -} - -static int pthread_mutex_destroy(pthread_mutex_t *m) -{ - DeleteCriticalSection(m); - return 0; -} - -#define pthread_mutex_getprioceiling(M, P) ENOTSUP -#define pthread_mutex_setprioceiling(M, P) ENOTSUP - -static int pthread_equal(pthread_t t1, pthread_t t2) -{ - return t1 == t2; -} - -static void pthread_testcancel(void); - -static int pthread_rwlock_init(pthread_rwlock_t *l, pthread_rwlockattr_t *a) -{ - (void) a; - InitializeSRWLock(l); - - return 0; -} - -static int pthread_rwlock_destroy(pthread_rwlock_t *l) -{ - (void) *l; - return 0; -} - -static int pthread_rwlock_rdlock(pthread_rwlock_t *l) -{ - pthread_testcancel(); - AcquireSRWLockShared(l); - - return 0; -} - -static int pthread_rwlock_wrlock(pthread_rwlock_t *l) -{ - pthread_testcancel(); - AcquireSRWLockExclusive(l); - - return 0; -} - -static void pthread_tls_init(void) -{ - _pthread_tls = TlsAlloc(); - - /* Cannot continue if out of indexes */ - if (_pthread_tls == TLS_OUT_OF_INDEXES) abort(); -} - -static void _pthread_cleanup_dest(pthread_t t) -{ - unsigned int i, j; - - for (j = 0; j < PTHREAD_DESTRUCTOR_ITERATIONS; j++) - { - int flag = 0; - - for (i = 0; i < t->keymax; i++) - { - void *val = t->keyval[i]; - - if (val) - { - pthread_rwlock_rdlock(&_pthread_key_lock); - if ((uintptr_t) _pthread_key_dest[i] > 1) - { - /* Call destructor */ - t->keyval[i] = NULL; - _pthread_key_dest[i](val); - flag = 1; - } - pthread_rwlock_unlock(&_pthread_key_lock); - } - } - - /* Nothing to do? */ - if (!flag) return; - } -} - -static pthread_t pthread_self(void) -{ - pthread_t t; - - _pthread_once_raw(&_pthread_tls_once, pthread_tls_init); - - t = (pthread_t)TlsGetValue(_pthread_tls); - - /* Main thread? */ - if (!t) - { - t = (pthread_t)malloc(sizeof(struct _pthread_v)); - - /* If cannot initialize main thread, then the only thing we can do is abort */ - if (!t) abort(); - - t->ret_arg = NULL; - t->func = NULL; - t->clean = NULL; - t->cancelled = 0; - t->p_state = PTHREAD_DEFAULT_ATTR; - t->keymax = 0; - t->keyval = NULL; - t->h = GetCurrentThread(); - - /* Save for later */ - TlsSetValue(_pthread_tls, t); - - if (setjmp(t->jb)) - { - /* Make sure we free ourselves if we are detached */ - if (!t->h) free(t); - - /* Time to die */ - _endthreadex(0); - } - } - - return t; -} - -static int pthread_rwlock_unlock(pthread_rwlock_t *l) -{ - void *state = *(void **)l; - - if (state == (void *) 1) - { - /* Known to be an exclusive lock */ - ReleaseSRWLockExclusive(l); - } - else - { - /* A shared unlock will work */ - ReleaseSRWLockShared(l); - } - - return 0; -} - - -static int pthread_rwlock_tryrdlock(pthread_rwlock_t *l) -{ - /* Get the current state of the lock */ - void *state = *(void **) l; - - if (!state) - { - /* Unlocked to locked */ - if (!_InterlockedCompareExchangePointer((volatile PVOID*) l, (void *)0x11, NULL)) return 0; - return EBUSY; - } - - /* A single writer exists */ - if (state == (void *) 1) return EBUSY; - - /* Multiple writers exist? */ - if ((uintptr_t) state & 14) return EBUSY; - - if (_InterlockedCompareExchangePointer((volatile PVOID*)l, (void *) ((uintptr_t)state + 16), state) == state) return 0; - - return EBUSY; -} - -static int pthread_rwlock_trywrlock(pthread_rwlock_t *l) -{ - /* Try to grab lock if it has no users */ - if (!_InterlockedCompareExchangePointer((volatile PVOID*)l, (void *)1, NULL)) return 0; - - return EBUSY; -} - -static unsigned long long _pthread_time_in_ms(void) -{ - struct __timeb64 tb; - - _ftime64(&tb); - - return tb.time * 1000 + tb.millitm; -} - -static unsigned long long _pthread_time_in_ms_from_timespec(const struct timespec *ts) -{ - unsigned long long t = ts->tv_sec * 1000; - t += ts->tv_nsec / 1000000; - - return t; -} - -static unsigned long long _pthread_rel_time_in_ms(const struct timespec *ts) -{ - unsigned long long t1 = _pthread_time_in_ms_from_timespec(ts); - unsigned long long t2 = _pthread_time_in_ms(); - - /* Prevent underflow */ - if (t1 < t2) return 0; - return t1 - t2; -} - -static int pthread_rwlock_timedrdlock(pthread_rwlock_t *l, const struct timespec *ts) -{ - unsigned long long ct = _pthread_time_in_ms(); - unsigned long long t = _pthread_time_in_ms_from_timespec(ts); - - pthread_testcancel(); - - /* Use a busy-loop */ - while (1) - { - /* Try to grab lock */ - if (!pthread_rwlock_tryrdlock(l)) return 0; - - /* Get current time */ - ct = _pthread_time_in_ms(); - - /* Have we waited long enough? */ - if (ct > t) return ETIMEDOUT; - } -} - -static int pthread_rwlock_timedwrlock(pthread_rwlock_t *l, const struct timespec *ts) -{ - unsigned long long ct = _pthread_time_in_ms(); - unsigned long long t = _pthread_time_in_ms_from_timespec(ts); - - pthread_testcancel(); - - /* Use a busy-loop */ - while (1) - { - /* Try to grab lock */ - if (!pthread_rwlock_trywrlock(l)) return 0; - - /* Get current time */ - ct = _pthread_time_in_ms(); - - /* Have we waited long enough? */ - if (ct > t) return ETIMEDOUT; - } -} - -static int pthread_get_concurrency(int *val) -{ - *val = _pthread_concur; - return 0; -} - -static int pthread_set_concurrency(int val) -{ - _pthread_concur = val; - return 0; -} - -#define pthread_getschedparam(T, P, S) ENOTSUP -#define pthread_setschedparam(T, P, S) ENOTSUP -#define pthread_getcpuclockid(T, C) ENOTSUP - -static int pthread_exit(void *res) -{ - pthread_t t = pthread_self(); - - t->ret_arg = res; - - _pthread_cleanup_dest(t); - - longjmp(t->jb, 1); -} - - -static void _pthread_invoke_cancel(void) -{ - _pthread_cleanup *pcup; - - _InterlockedDecrement(&_pthread_cancelling); - - /* Call cancel queue */ - for (pcup = pthread_self()->clean; pcup; pcup = pcup->next) - { - pcup->func(pcup->arg); - } - - pthread_exit(PTHREAD_CANCELED); -} - -static void pthread_testcancel(void) -{ - if (_pthread_cancelling) - { - pthread_t t = pthread_self(); - - if (t->cancelled && (t->p_state & PTHREAD_CANCEL_ENABLE)) - { - _pthread_invoke_cancel(); - } - } -} - - -static int pthread_cancel(pthread_t t) -{ - if (t->p_state & PTHREAD_CANCEL_ASYNCHRONOUS) - { - /* Dangerous asynchronous cancelling */ - CONTEXT ctxt; - - /* Already done? */ - if (t->cancelled) return ESRCH; - - ctxt.ContextFlags = CONTEXT_CONTROL; - - SuspendThread(t->h); - GetThreadContext(t->h, &ctxt); -#ifdef _M_X64 - ctxt.Rip = (uintptr_t) _pthread_invoke_cancel; -#else - ctxt.Eip = (uintptr_t) _pthread_invoke_cancel; -#endif - SetThreadContext(t->h, &ctxt); - - /* Also try deferred Cancelling */ - t->cancelled = 1; - - /* Notify everyone to look */ - _InterlockedIncrement(&_pthread_cancelling); - - ResumeThread(t->h); - } - else - { - /* Safe deferred Cancelling */ - t->cancelled = 1; - - /* Notify everyone to look */ - _InterlockedIncrement(&_pthread_cancelling); - } - - return 0; -} - -static unsigned _pthread_get_state(pthread_attr_t *attr, unsigned flag) -{ - return attr->p_state & flag; -} - -static int _pthread_set_state(pthread_attr_t *attr, unsigned flag, unsigned val) -{ - if (~flag & val) return EINVAL; - attr->p_state &= ~flag; - attr->p_state |= val; - - return 0; -} - -static int pthread_attr_init(pthread_attr_t *attr) -{ - attr->p_state = PTHREAD_DEFAULT_ATTR; - attr->stack = NULL; - attr->s_size = 0; - return 0; -} - -static int pthread_attr_destroy(pthread_attr_t *attr) -{ - /* No need to do anything */ - return 0; -} - - -static int pthread_attr_setdetachstate(pthread_attr_t *a, int flag) -{ - return _pthread_set_state(a, PTHREAD_CREATE_DETACHED, flag); -} - -static int pthread_attr_getdetachstate(pthread_attr_t *a, int *flag) -{ - *flag = _pthread_get_state(a, PTHREAD_CREATE_DETACHED); - return 0; -} - -static int pthread_attr_setinheritsched(pthread_attr_t *a, int flag) -{ - return _pthread_set_state(a, PTHREAD_INHERIT_SCHED, flag); -} - -static int pthread_attr_getinheritsched(pthread_attr_t *a, int *flag) -{ - *flag = _pthread_get_state(a, PTHREAD_INHERIT_SCHED); - return 0; -} - -static int pthread_attr_setscope(pthread_attr_t *a, int flag) -{ - return _pthread_set_state(a, PTHREAD_SCOPE_SYSTEM, flag); -} - -static int pthread_attr_getscope(pthread_attr_t *a, int *flag) -{ - *flag = _pthread_get_state(a, PTHREAD_SCOPE_SYSTEM); - return 0; -} - -static int pthread_attr_getstackaddr(pthread_attr_t *attr, void **stack) -{ - *stack = attr->stack; - return 0; -} - -static int pthread_attr_setstackaddr(pthread_attr_t *attr, void *stack) -{ - attr->stack = stack; - return 0; -} - -static int pthread_attr_getstacksize(pthread_attr_t *attr, size_t *size) -{ - *size = attr->s_size; - return 0; -} - -static int pthread_attr_setstacksize(pthread_attr_t *attr, size_t size) -{ - attr->s_size = size; - return 0; -} - -#define pthread_attr_getguardsize(A, S) ENOTSUP -#define pthread_attr_setgaurdsize(A, S) ENOTSUP -#define pthread_attr_getschedparam(A, S) ENOTSUP -#define pthread_attr_setschedparam(A, S) ENOTSUP -#define pthread_attr_getschedpolicy(A, S) ENOTSUP -#define pthread_attr_setschedpolicy(A, S) ENOTSUP - - -static int pthread_setcancelstate(int state, int *oldstate) -{ - pthread_t t = pthread_self(); - - if ((state & PTHREAD_CANCEL_ENABLE) != state) return EINVAL; - if (oldstate) *oldstate = t->p_state & PTHREAD_CANCEL_ENABLE; - t->p_state &= ~PTHREAD_CANCEL_ENABLE; - t->p_state |= state; - - return 0; -} - -static int pthread_setcanceltype(int type, int *oldtype) -{ - pthread_t t = pthread_self(); - - if ((type & PTHREAD_CANCEL_ASYNCHRONOUS) != type) return EINVAL; - if (oldtype) *oldtype = t->p_state & PTHREAD_CANCEL_ASYNCHRONOUS; - t->p_state &= ~PTHREAD_CANCEL_ASYNCHRONOUS; - t->p_state |= type; - - return 0; -} - -static unsigned int __stdcall pthread_create_wrapper(void *args) -{ - struct _pthread_v *tv = (struct _pthread_v*)args; - - _pthread_once_raw(&_pthread_tls_once, pthread_tls_init); - - TlsSetValue(_pthread_tls, tv); - - if (!setjmp(tv->jb)) - { - /* Call function and save return value */ - tv->ret_arg = tv->func(tv->ret_arg); - - /* Clean up destructors */ - _pthread_cleanup_dest(tv); - } - - /* If we exit too early, then we can race with create */ - while (tv->h == (HANDLE) -1) - { - YieldProcessor(); - _ReadWriteBarrier(); - } - - /* Make sure we free ourselves if we are detached */ - if (!tv->h) free(tv); - - return 0; -} - -static int pthread_create(pthread_t *th, pthread_attr_t *attr, void *(* func)(void *), void *arg) -{ - struct _pthread_v *tv = (struct _pthread_v*)malloc(sizeof(struct _pthread_v)); - unsigned ssize = 0; - - if (!tv) return 1; - - *th = tv; - - /* Save data in pthread_t */ - tv->ret_arg = arg; - tv->func = func; - tv->clean = NULL; - tv->cancelled = 0; - tv->p_state = PTHREAD_DEFAULT_ATTR; - tv->keymax = 0; - tv->keyval = NULL; - tv->h = (HANDLE) -1; - - if (attr) - { - tv->p_state = attr->p_state; - ssize = (unsigned)attr->s_size; - } - - /* Make sure tv->h has value of -1 */ - _ReadWriteBarrier(); - - tv->h = (HANDLE) _beginthreadex(NULL, ssize, pthread_create_wrapper, tv, 0, NULL); - - /* Failed */ - if (!tv->h) return 1; - - if (tv->p_state & PTHREAD_CREATE_DETACHED) - { - CloseHandle(tv->h); - _ReadWriteBarrier(); - tv->h = 0; - } - - return 0; -} - -static int pthread_join(pthread_t t, void **res) -{ - struct _pthread_v *tv = t; - - pthread_testcancel(); - - WaitForSingleObject(tv->h, INFINITE); - CloseHandle(tv->h); - - /* Obtain return value */ - if (res) *res = tv->ret_arg; - - free(tv); - - return 0; -} - -static int pthread_detach(pthread_t t) -{ - struct _pthread_v *tv = t; - - /* - * This can't race with thread exit because - * our call would be undefined if called on a dead thread. - */ - - CloseHandle(tv->h); - _ReadWriteBarrier(); - tv->h = 0; - - return 0; -} - -static int pthread_mutexattr_init(pthread_mutexattr_t *a) -{ - *a = 0; - return 0; -} - -static int pthread_mutexattr_destroy(pthread_mutexattr_t *a) -{ - (void) a; - return 0; -} - -static int pthread_mutexattr_gettype(pthread_mutexattr_t *a, int *type) -{ - *type = *a & 3; - - return 0; -} - -static int pthread_mutexattr_settype(pthread_mutexattr_t *a, int type) -{ - if ((unsigned) type > 3) return EINVAL; - *a &= ~3; - *a |= type; - - return 0; -} - -static int pthread_mutexattr_getpshared(pthread_mutexattr_t *a, int *type) -{ - *type = *a & 4; - - return 0; -} - -static int pthread_mutexattr_setpshared(pthread_mutexattr_t * a, int type) -{ - if ((type & 4) != type) return EINVAL; - - *a &= ~4; - *a |= type; - - return 0; -} - -static int pthread_mutexattr_getprotocol(pthread_mutexattr_t *a, int *type) -{ - *type = *a & (8 + 16); - - return 0; -} - -static int pthread_mutexattr_setprotocol(pthread_mutexattr_t *a, int type) -{ - if ((type & (8 + 16)) != 8 + 16) return EINVAL; - - *a &= ~(8 + 16); - *a |= type; - - return 0; -} - -static int pthread_mutexattr_getprioceiling(pthread_mutexattr_t *a, int * prio) -{ - *prio = *a / PTHREAD_PRIO_MULT; - return 0; -} - -static int pthread_mutexattr_setprioceiling(pthread_mutexattr_t *a, int prio) -{ - *a &= (PTHREAD_PRIO_MULT - 1); - *a += prio * PTHREAD_PRIO_MULT; - - return 0; -} - -static int pthread_mutex_timedlock(pthread_mutex_t *m, struct timespec *ts) -{ - unsigned long long t, ct; - - struct _pthread_crit_t - { - void *debug; - LONG count; - LONG r_count; - HANDLE owner; - HANDLE sem; - ULONG_PTR spin; - }; - - /* Try to lock it without waiting */ - if (!pthread_mutex_trylock(m)) return 0; - - ct = _pthread_time_in_ms(); - t = _pthread_time_in_ms_from_timespec(ts); - - while (1) - { - /* Have we waited long enough? */ - if (ct > t) return ETIMEDOUT; - - /* Wait on semaphore within critical section */ - WaitForSingleObject(((struct _pthread_crit_t *)m)->sem, (DWORD)(t - ct)); - - /* Try to grab lock */ - if (!pthread_mutex_trylock(m)) return 0; - - /* Get current time */ - ct = _pthread_time_in_ms(); - } -} - -#define _PTHREAD_BARRIER_FLAG (1<<30) - -static int pthread_barrier_destroy(pthread_barrier_t *b) -{ - EnterCriticalSection(&b->m); - - while (b->total > _PTHREAD_BARRIER_FLAG) - { - /* Wait until everyone exits the barrier */ - SleepConditionVariableCS(&b->cv, &b->m, INFINITE); - } - - LeaveCriticalSection(&b->m); - - DeleteCriticalSection(&b->m); - - return 0; -} - -static int pthread_barrier_init(pthread_barrier_t *b, void *attr, int count) -{ - /* Ignore attr */ - (void) attr; - - b->count = count; - b->total = 0; - - InitializeCriticalSection(&b->m); - InitializeConditionVariable(&b->cv); - - return 0; -} - -static int pthread_barrier_wait(pthread_barrier_t *b) -{ - EnterCriticalSection(&b->m); - - while (b->total > _PTHREAD_BARRIER_FLAG) - { - /* Wait until everyone exits the barrier */ - SleepConditionVariableCS(&b->cv, &b->m, INFINITE); - } - - /* Are we the first to enter? */ - if (b->total == _PTHREAD_BARRIER_FLAG) b->total = 0; - - b->total++; - - if (b->total == b->count) - { - b->total += _PTHREAD_BARRIER_FLAG - 1; - WakeAllConditionVariable(&b->cv); - - LeaveCriticalSection(&b->m); - - return 1; - } - else - { - while (b->total < _PTHREAD_BARRIER_FLAG) - { - /* Wait until enough threads enter the barrier */ - SleepConditionVariableCS(&b->cv, &b->m, INFINITE); - } - - b->total--; - - /* Get entering threads to wake up */ - if (b->total == _PTHREAD_BARRIER_FLAG) WakeAllConditionVariable(&b->cv); - - LeaveCriticalSection(&b->m); - - return 0; - } -} - -static int pthread_barrierattr_init(void **attr) -{ - *attr = NULL; - return 0; -} - -static int pthread_barrierattr_destroy(void **attr) -{ - /* Ignore attr */ - (void) attr; - - return 0; -} - -static int pthread_barrierattr_setpshared(void **attr, int s) -{ - INT_PTR ip = s; - *attr = (void *) ip; - return 0; -} - -static int pthread_barrierattr_getpshared(void **attr, int *s) -{ - *s = (int) (size_t) *attr; - - return 0; -} - -static int pthread_key_create(pthread_key_t *key, void (* dest)(void *)) -{ - unsigned int i; - long nmax; - void (**d)(void *); - - if (!key) return EINVAL; - - pthread_rwlock_wrlock(&_pthread_key_lock); - - for (i = _pthread_key_sch; i < _pthread_key_max; i++) - { - if (!_pthread_key_dest[i]) - { - *key = i; - if (dest) - { - _pthread_key_dest[i] = dest; - } - else - { - _pthread_key_dest[i] = (void(*)(void *))1; - } - pthread_rwlock_unlock(&_pthread_key_lock); - - return 0; - } - } - - for (i = 0; i < _pthread_key_sch; i++) - { - if (!_pthread_key_dest[i]) - { - *key = i; - if (dest) - { - _pthread_key_dest[i] = dest; - } - else - { - _pthread_key_dest[i] = (void(*)(void *))1; - } - pthread_rwlock_unlock(&_pthread_key_lock); - - return 0; - } - } - - if (!_pthread_key_max) _pthread_key_max = 1; - if (_pthread_key_max == PTHREAD_KEYS_MAX) - { - pthread_rwlock_unlock(&_pthread_key_lock); - - return ENOMEM; - } - - nmax = _pthread_key_max * 2; - if (nmax > PTHREAD_KEYS_MAX) nmax = PTHREAD_KEYS_MAX; - - /* No spare room anywhere */ - d = (void(**)(void*))realloc(_pthread_key_dest, nmax * sizeof(*d)); - if (!d) - { - pthread_rwlock_unlock(&_pthread_key_lock); - - return ENOMEM; - } - - /* Clear new region */ - memset((void *) &d[_pthread_key_max], 0, (nmax-_pthread_key_max)*sizeof(void *)); - - /* Use new region */ - _pthread_key_dest = d; - _pthread_key_sch = _pthread_key_max + 1; - *key = _pthread_key_max; - _pthread_key_max = nmax; - - if (dest) - { - _pthread_key_dest[*key] = dest; - } - else - { - _pthread_key_dest[*key] = (void(*)(void *))1; - } - - pthread_rwlock_unlock(&_pthread_key_lock); - - return 0; -} - -static int pthread_key_delete(pthread_key_t key) -{ - if (key > _pthread_key_max) return EINVAL; - if (!_pthread_key_dest) return EINVAL; - - pthread_rwlock_wrlock(&_pthread_key_lock); - _pthread_key_dest[key] = NULL; - - /* Start next search from our location */ - if (_pthread_key_sch > key) _pthread_key_sch = key; - - pthread_rwlock_unlock(&_pthread_key_lock); - - return 0; -} - -static void *pthread_getspecific(pthread_key_t key) -{ - pthread_t t = pthread_self(); - - if (key >= t->keymax) return NULL; - - return t->keyval[key]; - -} - -static int pthread_setspecific(pthread_key_t key, const void *value) -{ - pthread_t t = pthread_self(); - - if (key > t->keymax) - { - int keymax = (key + 1) * 2; - void **kv = (void**)realloc(t->keyval, keymax * sizeof(void *)); - - if (!kv) return ENOMEM; - - /* Clear new region */ - memset(&kv[t->keymax], 0, (keymax - t->keymax)*sizeof(void*)); - - t->keyval = kv; - t->keymax = keymax; - } - - t->keyval[key] = (void *) value; - - return 0; -} - - -static int pthread_spin_init(pthread_spinlock_t *l, int pshared) -{ - (void) pshared; - - *l = 0; - return 0; -} - -static int pthread_spin_destroy(pthread_spinlock_t *l) -{ - (void) l; - return 0; -} - -/* No-fair spinlock due to lack of knowledge of thread number */ -static int pthread_spin_lock(pthread_spinlock_t *l) -{ - while (_InterlockedExchange(l, EBUSY)) - { - /* Don't lock the bus whilst waiting */ - while (*l) - { - YieldProcessor(); - - /* Compiler barrier. Prevent caching of *l */ - _ReadWriteBarrier(); - } - } - - return 0; -} - -static int pthread_spin_trylock(pthread_spinlock_t *l) -{ - return _InterlockedExchange(l, EBUSY); -} - -static int pthread_spin_unlock(pthread_spinlock_t *l) -{ - /* Compiler barrier. The store below acts with release symmantics */ - _ReadWriteBarrier(); - - *l = 0; - - return 0; -} - -static int pthread_cond_init(pthread_cond_t *c, pthread_condattr_t *a) -{ - (void) a; - - InitializeConditionVariable(c); - return 0; -} - -static int pthread_cond_signal(pthread_cond_t *c) -{ - WakeConditionVariable(c); - return 0; -} - -static int pthread_cond_broadcast(pthread_cond_t *c) -{ - WakeAllConditionVariable(c); - return 0; -} - -static int pthread_cond_wait(pthread_cond_t *c, pthread_mutex_t *m) -{ - pthread_testcancel(); - SleepConditionVariableCS(c, m, INFINITE); - return 0; -} - -static int pthread_cond_destroy(pthread_cond_t *c) -{ - (void) c; - return 0; -} - -static int pthread_cond_timedwait(pthread_cond_t *c, pthread_mutex_t *m, struct timespec *t) -{ - unsigned long long tm = _pthread_rel_time_in_ms(t); - - pthread_testcancel(); - - if (!SleepConditionVariableCS(c, m, (DWORD)(tm))) return ETIMEDOUT; - - /* We can have a spurious wakeup after the timeout */ - if (!_pthread_rel_time_in_ms(t)) return ETIMEDOUT; - - return 0; -} - -static int pthread_condattr_destroy(pthread_condattr_t *a) -{ - (void) a; - return 0; -} - -#define pthread_condattr_getclock(A, C) ENOTSUP -#define pthread_condattr_setclock(A, C) ENOTSUP - -static int pthread_condattr_init(pthread_condattr_t *a) -{ - *a = 0; - return 0; -} - -static int pthread_condattr_getpshared(pthread_condattr_t *a, int *s) -{ - *s = *a; - return 0; -} - -static int pthread_condattr_setpshared(pthread_condattr_t *a, int s) -{ - *a = s; - return 0; -} - -static int pthread_rwlockattr_destroy(pthread_rwlockattr_t *a) -{ - (void) a; - return 0; -} - -static int pthread_rwlockattr_init(pthread_rwlockattr_t *a) -{ - *a = 0; -} - -static int pthread_rwlockattr_getpshared(pthread_rwlockattr_t *a, int *s) -{ - *s = *a; - return 0; -} - -static int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *a, int s) -{ - *a = s; - return 0; -} - - -/* No fork() in windows - so ignore this */ -#define pthread_atfork(F1,F2,F3) 0 - -/* Windows has rudimentary signals support */ -#define pthread_kill(T, S) 0 -#define pthread_sigmask(H, S1, S2) 0 - - -/* Wrap cancellation points */ -#define accept(...) (pthread_testcancel(), accept(__VA_ARGS__)) -#define aio_suspend(...) (pthread_testcancel(), aio_suspend(__VA_ARGS__)) -#define clock_nanosleep(...) (pthread_testcancel(), clock_nanosleep(__VA_ARGS__)) -#define close(...) (pthread_testcancel(), close(__VA_ARGS__)) -#define connect(...) (pthread_testcancel(), connect(__VA_ARGS__)) -#define creat(...) (pthread_testcancel(), creat(__VA_ARGS__)) -#define fcntl(...) (pthread_testcancel(), fcntl(__VA_ARGS__)) -#define fdatasync(...) (pthread_testcancel(), fdatasync(__VA_ARGS__)) -#define fsync(...) (pthread_testcancel(), fsync(__VA_ARGS__)) -#define getmsg(...) (pthread_testcancel(), getmsg(__VA_ARGS__)) -#define getpmsg(...) (pthread_testcancel(), getpmsg(__VA_ARGS__)) -#define lockf(...) (pthread_testcancel(), lockf(__VA_ARGS__)) -#define mg_receive(...) (pthread_testcancel(), mg_receive(__VA_ARGS__)) -#define mg_send(...) (pthread_testcancel(), mg_send(__VA_ARGS__)) -#define mg_timedreceive(...) (pthread_testcancel(), mg_timedreceive(__VA_ARGS__)) -#define mg_timessend(...) (pthread_testcancel(), mg_timedsend(__VA_ARGS__)) -#define msgrcv(...) (pthread_testcancel(), msgrecv(__VA_ARGS__)) -#define msgsnd(...) (pthread_testcancel(), msgsnd(__VA_ARGS__)) -#define msync(...) (pthread_testcancel(), msync(__VA_ARGS__)) -#define nanosleep(...) (pthread_testcancel(), nanosleep(__VA_ARGS__)) -#define open(...) (pthread_testcancel(), open(__VA_ARGS__)) -#define pause(...) (pthread_testcancel(), pause(__VA_ARGS__)) -#define poll(...) (pthread_testcancel(), poll(__VA_ARGS__)) -#define pread(...) (pthread_testcancel(), pread(__VA_ARGS__)) -#define pselect(...) (pthread_testcancel(), pselect(__VA_ARGS__)) -#define putmsg(...) (pthread_testcancel(), putmsg(__VA_ARGS__)) -#define putpmsg(...) (pthread_testcancel(), putpmsg(__VA_ARGS__)) -#define pwrite(...) (pthread_testcancel(), pwrite(__VA_ARGS__)) -#define read(...) (pthread_testcancel(), read(__VA_ARGS__)) -#define readv(...) (pthread_testcancel(), readv(__VA_ARGS__)) -#define recv(...) (pthread_testcancel(), recv(__VA_ARGS__)) -#define recvfrom(...) (pthread_testcancel(), recvfrom(__VA_ARGS__)) -#define recvmsg(...) (pthread_testcancel(), recvmsg(__VA_ARGS__)) -#define select(...) (pthread_testcancel(), select(__VA_ARGS__)) -#define sem_timedwait(...) (pthread_testcancel(), sem_timedwait(__VA_ARGS__)) -#define sem_wait(...) (pthread_testcancel(), sem_wait(__VA_ARGS__)) -#define send(...) (pthread_testcancel(), send(__VA_ARGS__)) -#define sendmsg(...) (pthread_testcancel(), sendmsg(__VA_ARGS__)) -#define sendto(...) (pthread_testcancel(), sendto(__VA_ARGS__)) -#define sigpause(...) (pthread_testcancel(), sigpause(__VA_ARGS__)) -#define sigsuspend(...) (pthread_testcancel(), sigsuspend(__VA_ARGS__)) -#define sigwait(...) (pthread_testcancel(), sigwait(__VA_ARGS__)) -#define sigwaitinfo(...) (pthread_testcancel(), sigwaitinfo(__VA_ARGS__)) -//#define sleep(...) (pthread_testcancel(), sleep(__VA_ARGS__)) -//#define Sleep(...) (pthread_testcancel(), Sleep(__VA_ARGS__)) -#define system(...) (pthread_testcancel(), system(__VA_ARGS__)) - - -#define access(...) (pthread_testcancel(), access(__VA_ARGS__)) -#define asctime(...) (pthread_testcancel(), asctime(__VA_ARGS__)) -#define asctime_r(...) (pthread_testcancel(), asctime_r(__VA_ARGS__)) -#define catclose(...) (pthread_testcancel(), catclose(__VA_ARGS__)) -#define catgets(...) (pthread_testcancel(), catgets(__VA_ARGS__)) -#define catopen(...) (pthread_testcancel(), catopen(__VA_ARGS__)) -#define closedir(...) (pthread_testcancel(), closedir(__VA_ARGS__)) -#define closelog(...) (pthread_testcancel(), closelog(__VA_ARGS__)) -#define ctermid(...) (pthread_testcancel(), ctermid(__VA_ARGS__)) -#define ctime(...) (pthread_testcancel(), ctime(__VA_ARGS__)) -#define ctime_r(...) (pthread_testcancel(), ctime_r(__VA_ARGS__)) -#define dbm_close(...) (pthread_testcancel(), dbm_close(__VA_ARGS__)) -#define dbm_delete(...) (pthread_testcancel(), dbm_delete(__VA_ARGS__)) -#define dbm_fetch(...) (pthread_testcancel(), dbm_fetch(__VA_ARGS__)) -#define dbm_nextkey(...) (pthread_testcancel(), dbm_nextkey(__VA_ARGS__)) -#define dbm_open(...) (pthread_testcancel(), dbm_open(__VA_ARGS__)) -#define dbm_store(...) (pthread_testcancel(), dbm_store(__VA_ARGS__)) -#define dlclose(...) (pthread_testcancel(), dlclose(__VA_ARGS__)) -#define dlopen(...) (pthread_testcancel(), dlopen(__VA_ARGS__)) -#define endgrent(...) (pthread_testcancel(), endgrent(__VA_ARGS__)) -#define endhostent(...) (pthread_testcancel(), endhostent(__VA_ARGS__)) -#define endnetent(...) (pthread_testcancel(), endnetent(__VA_ARGS__)) -#define endprotoent(...) (pthread_testcancel(), endprotoend(__VA_ARGS__)) -#define endpwent(...) (pthread_testcancel(), endpwent(__VA_ARGS__)) -#define endservent(...) (pthread_testcancel(), endservent(__VA_ARGS__)) -#define endutxent(...) (pthread_testcancel(), endutxent(__VA_ARGS__)) -#define fclose(...) (pthread_testcancel(), fclose(__VA_ARGS__)) -#define fflush(...) (pthread_testcancel(), fflush(__VA_ARGS__)) -#define fgetc(...) (pthread_testcancel(), fgetc(__VA_ARGS__)) -#define fgetpos(...) (pthread_testcancel(), fgetpos(__VA_ARGS__)) -#define fgets(...) (pthread_testcancel(), fgets(__VA_ARGS__)) -#define fgetwc(...) (pthread_testcancel(), fgetwc(__VA_ARGS__)) -#define fgetws(...) (pthread_testcancel(), fgetws(__VA_ARGS__)) -#define fmtmsg(...) (pthread_testcancel(), fmtmsg(__VA_ARGS__)) -#define fopen(...) (pthread_testcancel(), fopen(__VA_ARGS__)) -#define fpathconf(...) (pthread_testcancel(), fpathconf(__VA_ARGS__)) -#define fprintf(...) (pthread_testcancel(), fprintf(__VA_ARGS__)) -#define fputc(...) (pthread_testcancel(), fputc(__VA_ARGS__)) -#define fputs(...) (pthread_testcancel(), fputs(__VA_ARGS__)) -#define fputwc(...) (pthread_testcancel(), fputwc(__VA_ARGS__)) -#define fputws(...) (pthread_testcancel(), fputws(__VA_ARGS__)) -#define fread(...) (pthread_testcancel(), fread(__VA_ARGS__)) -#define freopen(...) (pthread_testcancel(), freopen(__VA_ARGS__)) -#define fscanf(...) (pthread_testcancel(), fscanf(__VA_ARGS__)) -#define fseek(...) (pthread_testcancel(), fseek(__VA_ARGS__)) -#define fseeko(...) (pthread_testcancel(), fseeko(__VA_ARGS__)) -#define fsetpos(...) (pthread_testcancel(), fsetpos(__VA_ARGS__)) -#define fstat(...) (pthread_testcancel(), fstat(__VA_ARGS__)) -#define ftell(...) (pthread_testcancel(), ftell(__VA_ARGS__)) -#define ftello(...) (pthread_testcancel(), ftello(__VA_ARGS__)) -#define ftw(...) (pthread_testcancel(), ftw(__VA_ARGS__)) -#define fwprintf(...) (pthread_testcancel(), fwprintf(__VA_ARGS__)) -#define fwrite(...) (pthread_testcancel(), fwrite(__VA_ARGS__)) -#define fwscanf(...) (pthread_testcancel(), fwscanf(__VA_ARGS__)) -#define getaddrinfo(...) (pthread_testcancel(), getaddrinfo(__VA_ARGS__)) -#define getc(...) (pthread_testcancel(), getc(__VA_ARGS__)) -#define getc_unlocked(...) (pthread_testcancel(), getc_unlocked(__VA_ARGS__)) -#define getchar(...) (pthread_testcancel(), getchar(__VA_ARGS__)) -#define getchar_unlocked(...) (pthread_testcancel(), getchar_unlocked(__VA_ARGS__)) -#define getcwd(...) (pthread_testcancel(), getcwd(__VA_ARGS__)) -#define getdate(...) (pthread_testcancel(), getdate(__VA_ARGS__)) -#define getgrent(...) (pthread_testcancel(), getgrent(__VA_ARGS__)) -#define getgrgid(...) (pthread_testcancel(), getgrgid(__VA_ARGS__)) -#define getgrgid_r(...) (pthread_testcancel(), getgrgid_r(__VA_ARGS__)) -#define gergrnam(...) (pthread_testcancel(), getgrnam(__VA_ARGS__)) -#define getgrnam_r(...) (pthread_testcancel(), getgrnam_r(__VA_ARGS__)) -#define gethostbyaddr(...) (pthread_testcancel(), gethostbyaddr(__VA_ARGS__)) -#define gethostbyname(...) (pthread_testcancel(), gethostbyname(__VA_ARGS__)) -#define gethostent(...) (pthread_testcancel(), gethostent(__VA_ARGS__)) -#define gethostid(...) (pthread_testcancel(), gethostid(__VA_ARGS__)) -#define gethostname(...) (pthread_testcancel(), gethostname(__VA_ARGS__)) -#define getlogin(...) (pthread_testcancel(), getlogin(__VA_ARGS__)) -#define getlogin_r(...) (pthread_testcancel(), getlogin_r(__VA_ARGS__)) -#define getnameinfo(...) (pthread_testcancel(), getnameinfo(__VA_ARGS__)) -#define getnetbyaddr(...) (pthread_testcancel(), getnetbyaddr(__VA_ARGS__)) -#define getnetbyname(...) (pthread_testcancel(), getnetbyname(__VA_ARGS__)) -#define getnetent(...) (pthread_testcancel(), getnetent(__VA_ARGS__)) -#define getopt(...) (pthread_testcancel(), getopt(__VA_ARGS__)) -#define getprotobyname(...) (pthread_testcancel(), getprotobyname(__VA_ARGS__)) -#define getprotobynumber(...) (pthread_testcancel(), getprotobynumber(__VA_ARGS__)) -#define getprotoent(...) (pthread_testcancel(), getprotoent(__VA_ARGS__)) -#define getpwent(...) (pthread_testcancel(), getpwent(__VA_ARGS__)) -#define getpwnam(...) (pthread_testcancel(), getpwnam(__VA_ARGS__)) -#define getpwnam_r(...) (pthread_testcancel(), getpwnam_r(__VA_ARGS__)) -#define getpwuid(...) (pthread_testcancel(), getpwuid(__VA_ARGS__)) -#define getpwuid_r(...) (pthread_testcancel(), getpwuid_r(__VA_ARGS__)) -#define gets(...) (pthread_testcancel(), gets(__VA_ARGS__)) -#define getservbyname(...) (pthread_testcancel(), getservbyname(__VA_ARGS__)) -#define getservbyport(...) (pthread_testcancel(), getservbyport(__VA_ARGS__)) -#define getservent(...) (pthread_testcancel(), getservent(__VA_ARGS__)) -#define getutxent(...) (pthread_testcancel(), getutxent(__VA_ARGS__)) -#define getutxid(...) (pthread_testcancel(), getutxid(__VA_ARGS__)) -#define getutxline(...) (pthread_testcancel(), getutxline(__VA_ARGS__)) -#undef getwc -#define getwc(...) (pthread_testcancel(), getwc(__VA_ARGS__)) -#undef getwchar -#define getwchar(...) (pthread_testcancel(), getwchar(__VA_ARGS__)) -#define getwd(...) (pthread_testcancel(), getwd(__VA_ARGS__)) -#define glob(...) (pthread_testcancel(), glob(__VA_ARGS__)) -#define iconv_close(...) (pthread_testcancel(), iconv_close(__VA_ARGS__)) -#define iconv_open(...) (pthread_testcancel(), iconv_open(__VA_ARGS__)) -#define ioctl(...) (pthread_testcancel(), ioctl(__VA_ARGS__)) -#define link(...) (pthread_testcancel(), link(__VA_ARGS__)) -#define localtime(...) (pthread_testcancel(), localtime(__VA_ARGS__)) -#define localtime_r(...) (pthread_testcancel(), localtime_r(__VA_ARGS__)) -#define lseek(...) (pthread_testcancel(), lseek(__VA_ARGS__)) -#define lstat(...) (pthread_testcancel(), lstat(__VA_ARGS__)) -#define mkstemp(...) (pthread_testcancel(), mkstemp(__VA_ARGS__)) -#define nftw(...) (pthread_testcancel(), nftw(__VA_ARGS__)) -#define opendir(...) (pthread_testcancel(), opendir(__VA_ARGS__)) -#define openlog(...) (pthread_testcancel(), openlog(__VA_ARGS__)) -#define pathconf(...) (pthread_testcancel(), pathconf(__VA_ARGS__)) -#define pclose(...) (pthread_testcancel(), pclose(__VA_ARGS__)) -#define perror(...) (pthread_testcancel(), perror(__VA_ARGS__)) -#define popen(...) (pthread_testcancel(), popen(__VA_ARGS__)) -#define posix_fadvise(...) (pthread_testcancel(), posix_fadvise(__VA_ARGS__)) -#define posix_fallocate(...) (pthread_testcancel(), posix_fallocate(__VA_ARGS__)) -#define posix_madvise(...) (pthread_testcancel(), posix_madvise(__VA_ARGS__)) -#define posix_openpt(...) (pthread_testcancel(), posix_openpt(__VA_ARGS__)) -#define posix_spawn(...) (pthread_testcancel(), posix_spawn(__VA_ARGS__)) -#define posix_spawnp(...) (pthread_testcancel(), posix_spawnp(__VA_ARGS__)) -#define posix_trace_clear(...) (pthread_testcancel(), posix_trace_clear(__VA_ARGS__)) -#define posix_trace_close(...) (pthread_testcancel(), posix_trace_close(__VA_ARGS__)) -#define posix_trace_create(...) (pthread_testcancel(), posix_trace_create(__VA_ARGS__)) -#define posix_trace_create_withlog(...) (pthread_testcancel(), posix_trace_create_withlog(__VA_ARGS__)) -#define posix_trace_eventtypelist_getne(...) (pthread_testcancel(), posix_trace_eventtypelist_getne(__VA_ARGS__)) -#define posix_trace_eventtypelist_rewin(...) (pthread_testcancel(), posix_trace_eventtypelist_rewin(__VA_ARGS__)) -#define posix_trace_flush(...) (pthread_testcancel(), posix_trace_flush(__VA_ARGS__)) -#define posix_trace_get_attr(...) (pthread_testcancel(), posix_trace_get_attr(__VA_ARGS__)) -#define posix_trace_get_filter(...) (pthread_testcancel(), posix_trace_get_filter(__VA_ARGS__)) -#define posix_trace_get_status(...) (pthread_testcancel(), posix_trace_get_status(__VA_ARGS__)) -#define posix_trace_getnext_event(...) (pthread_testcancel(), posix_trace_getnext_event(__VA_ARGS__)) -#define posix_trace_open(...) (pthread_testcancel(), posix_trace_open(__VA_ARGS__)) -#define posix_trace_rewind(...) (pthread_testcancel(), posix_trace_rewind(__VA_ARGS__)) -#define posix_trace_setfilter(...) (pthread_testcancel(), posix_trace_setfilter(__VA_ARGS__)) -#define posix_trace_shutdown(...) (pthread_testcancel(), posix_trace_shutdown(__VA_ARGS__)) -#define posix_trace_timedgetnext_event(...) (pthread_testcancel(), posix_trace_timedgetnext_event(__VA_ARGS__)) -#define posix_typed_mem_open(...) (pthread_testcancel(), posix_typed_mem_open(__VA_ARGS__)) -#define printf(...) (pthread_testcancel(), printf(__VA_ARGS__)) -#define putc(...) (pthread_testcancel(), putc(__VA_ARGS__)) -#define putc_unlocked(...) (pthread_testcancel(), putc_unlocked(__VA_ARGS__)) -#define putchar(...) (pthread_testcancel(), putchar(__VA_ARGS__)) -#define putchar_unlocked(...) (pthread_testcancel(), putchar_unlocked(__VA_ARGS__)) -#define puts(...) (pthread_testcancel(), puts(__VA_ARGS__)) -#define pututxline(...) (pthread_testcancel(), pututxline(__VA_ARGS__)) -#undef putwc -#define putwc(...) (pthread_testcancel(), putwc(__VA_ARGS__)) -#undef putwchar -#define putwchar(...) (pthread_testcancel(), putwchar(__VA_ARGS__)) -#define readdir(...) (pthread_testcancel(), readdir(__VA_ARSG__)) -#define readdir_r(...) (pthread_testcancel(), readdir_r(__VA_ARGS__)) -#define remove(...) (pthread_testcancel(), remove(__VA_ARGS__)) -#define rename(...) (pthread_testcancel(), rename(__VA_ARGS__)) -#define rewind(...) (pthread_testcancel(), rewind(__VA_ARGS__)) -#define rewinddir(...) (pthread_testcancel(), rewinddir(__VA_ARGS__)) -#define scanf(...) (pthread_testcancel(), scanf(__VA_ARGS__)) -#define seekdir(...) (pthread_testcancel(), seekdir(__VA_ARGS__)) -#define semop(...) (pthread_testcancel(), semop(__VA_ARGS__)) -#define setgrent(...) (pthread_testcancel(), setgrent(__VA_ARGS__)) -#define sethostent(...) (pthread_testcancel(), sethostemt(__VA_ARGS__)) -#define setnetent(...) (pthread_testcancel(), setnetent(__VA_ARGS__)) -#define setprotoent(...) (pthread_testcancel(), setprotoent(__VA_ARGS__)) -#define setpwent(...) (pthread_testcancel(), setpwent(__VA_ARGS__)) -#define setservent(...) (pthread_testcancel(), setservent(__VA_ARGS__)) -#define setutxent(...) (pthread_testcancel(), setutxent(__VA_ARGS__)) -#define stat(...) (pthread_testcancel(), stat(__VA_ARGS__)) -#define strerror(...) (pthread_testcancel(), strerror(__VA_ARGS__)) -#define strerror_r(...) (pthread_testcancel(), strerror_r(__VA_ARGS__)) -#define strftime(...) (pthread_testcancel(), strftime(__VA_ARGS__)) -#define symlink(...) (pthread_testcancel(), symlink(__VA_ARGS__)) -#define sync(...) (pthread_testcancel(), sync(__VA_ARGS__)) -#define syslog(...) (pthread_testcancel(), syslog(__VA_ARGS__)) -#define tmpfile(...) (pthread_testcancel(), tmpfile(__VA_ARGS__)) -#define tmpnam(...) (pthread_testcancel(), tmpnam(__VA_ARGS__)) -#define ttyname(...) (pthread_testcancel(), ttyname(__VA_ARGS__)) -#define ttyname_r(...) (pthread_testcancel(), ttyname_r(__VA_ARGS__)) -#define tzset(...) (pthread_testcancel(), tzset(__VA_ARGS__)) -#define ungetc(...) (pthread_testcancel(), ungetc(__VA_ARGS__)) -#define ungetwc(...) (pthread_testcancel(), ungetwc(__VA_ARGS__)) -#define unlink(...) (pthread_testcancel(), unlink(__VA_ARGS__)) -#define vfprintf(...) (pthread_testcancel(), vfprintf(__VA_ARGS__)) -#define vfwprintf(...) (pthread_testcancel(), vfwprintf(__VA_ARGS__)) -#define vprintf(...) (pthread_testcancel(), vprintf(__VA_ARGS__)) -#define vwprintf(...) (pthread_testcancel(), vwprintf(__VA_ARGS__)) -#define wcsftime(...) (pthread_testcancel(), wcsftime(__VA_ARGS__)) -#define wordexp(...) (pthread_testcancel(), wordexp(__VA_ARGS__)) -#define wprintf(...) (pthread_testcancel(), wprintf(__VA_ARGS__)) -#define wscanf(...) (pthread_testcancel(), wscanf(__VA_ARGS__)) - -#endif /* WIN_PTHREADS */ +/* + * Posix Threads library for Microsoft Windows + * + * Use at own risk, there is no implied warranty to this code. + * It uses undocumented features of Microsoft Windows that can change + * at any time in the future. + * + * (C) 2010 Lockless Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Lockless Inc. nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AN + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * You may want to use the MingW64 winpthreads library instead. + * It is based on this, but adds error checking. + */ + +/* + * Version 1.0.1 Released 2 Feb 2012 + * Fixes pthread_barrier_destroy() to wait for threads to exit the barrier. + */ + +#ifndef WIN_PTHREADS +#define WIN_PTHREADS + + +#include +#include +#include +#include +#include +#include + +#define PTHREAD_CANCEL_DISABLE 0 +#define PTHREAD_CANCEL_ENABLE 0x01 + +#define PTHREAD_CANCEL_DEFERRED 0 +#define PTHREAD_CANCEL_ASYNCHRONOUS 0x02 + +#define PTHREAD_CREATE_JOINABLE 0 +#define PTHREAD_CREATE_DETACHED 0x04 + +#define PTHREAD_EXPLICT_SCHED 0 +#define PTHREAD_INHERIT_SCHED 0x08 + +#define PTHREAD_SCOPE_PROCESS 0 +#define PTHREAD_SCOPE_SYSTEM 0x10 + +#define PTHREAD_DEFAULT_ATTR (PTHREAD_CANCEL_ENABLE) + +#define PTHREAD_CANCELED ((void *)((UINT_PTR)0xDEADBEEF)) + +#define PTHREAD_ONCE_INIT 0 +#define PTHREAD_MUTEX_INITIALIZER {(void*)-1,-1,0,0,0,0} +#define PTHREAD_RWLOCK_INITIALIZER {0} +#define PTHREAD_COND_INITIALIZER {0} +#define PTHREAD_BARRIER_INITIALIZER \ + {0,0,PTHREAD_MUTEX_INITIALIZER,PTHREAD_COND_INITIALIZER} +#define PTHREAD_SPINLOCK_INITIALIZER 0 + +#define PTHREAD_DESTRUCTOR_ITERATIONS 256 +#define PTHREAD_KEYS_MAX (1<<20) + +#define PTHREAD_MUTEX_NORMAL 0 +#define PTHREAD_MUTEX_ERRORCHECK 1 +#define PTHREAD_MUTEX_RECURSIVE 2 +#define PTHREAD_MUTEX_DEFAULT 3 +#define PTHREAD_MUTEX_SHARED 4 +#define PTHREAD_MUTEX_PRIVATE 0 +#define PTHREAD_PRIO_NONE 0 +#define PTHREAD_PRIO_INHERIT 8 +#define PTHREAD_PRIO_PROTECT 16 +#define PTHREAD_PRIO_MULT 32 +#define PTHREAD_PROCESS_SHARED 0 +#define PTHREAD_PROCESS_PRIVATE 1 + +#define PTHREAD_BARRIER_SERIAL_THREAD 1 + + +///* Windows doesn't have this, so declare it ourselves. */ +//struct timespec +//{ +// /* long long in windows is the same as long in unix for 64bit */ +// long long tv_sec; +// long long tv_nsec; +//}; + +typedef struct _pthread_cleanup _pthread_cleanup; +struct _pthread_cleanup +{ + void (*func)(void *); + void *arg; + _pthread_cleanup *next; +}; + +struct _pthread_v +{ + void *ret_arg; + void *(* func)(void *); + _pthread_cleanup *clean; + HANDLE h; + int cancelled; + unsigned p_state; + unsigned int keymax; + void **keyval; + + jmp_buf jb; +}; + +typedef struct _pthread_v *pthread_t; + +typedef struct pthread_barrier_t pthread_barrier_t; +struct pthread_barrier_t +{ + int count; + int total; + CRITICAL_SECTION m; + CONDITION_VARIABLE cv; +}; + +typedef struct pthread_attr_t pthread_attr_t; +struct pthread_attr_t +{ + unsigned p_state; + void *stack; + size_t s_size; +}; + +typedef long pthread_once_t; +typedef unsigned pthread_mutexattr_t; +typedef SRWLOCK pthread_rwlock_t; +typedef CRITICAL_SECTION pthread_mutex_t; +typedef unsigned pthread_key_t; +typedef void *pthread_barrierattr_t; +typedef long pthread_spinlock_t; +typedef int pthread_condattr_t; +typedef CONDITION_VARIABLE pthread_cond_t; +typedef int pthread_rwlockattr_t; + +volatile long _pthread_cancelling; + +int _pthread_concur; + +/* Will default to zero as needed */ +pthread_once_t _pthread_tls_once; +DWORD _pthread_tls; + +/* Note initializer is zero, so this works */ +pthread_rwlock_t _pthread_key_lock; +unsigned int _pthread_key_max; +unsigned int _pthread_key_sch; +void (**_pthread_key_dest)(void *); + +static int pthread_rwlock_unlock(pthread_rwlock_t *l); + + +#define pthread_cleanup_push(F, A)\ +{\ + const _pthread_cleanup _pthread_cup = {(F), (A), pthread_self()->clean};\ + _ReadWriteBarrier();\ + pthread_self()->clean = (_pthread_cleanup *) &_pthread_cup;\ + _ReadWriteBarrier() + +/* Note that if async cancelling is used, then there is a race here */ +#define pthread_cleanup_pop(E)\ + (pthread_self()->clean = _pthread_cup.next, (E?_pthread_cup.func(_pthread_cup.arg):0));} + +static void _pthread_once_cleanup(void *o) +{ + *(pthread_once_t*)o = 0; +} + +static pthread_t pthread_self(void); +static int pthread_once(pthread_once_t *o, void (*func)(void)) +{ + long state = *o; + + _ReadWriteBarrier(); + + while (state != 1) + { + if (!state) + { + if (!_InterlockedCompareExchange(o, 2, 0)) + { + /* Success */ + pthread_cleanup_push(_pthread_once_cleanup, o); + func(); + pthread_cleanup_pop(0); + + /* Mark as done */ + *o = 1; + + return 0; + } + } + + YieldProcessor(); + + _ReadWriteBarrier(); + + state = *o; + } + + /* Done */ + return 0; +} + +static int _pthread_once_raw(pthread_once_t *o, void (*func)(void)) +{ + long state = *o; + + _ReadWriteBarrier(); + + while (state != 1) + { + if (!state) + { + if (!_InterlockedCompareExchange(o, 2, 0)) + { + /* Success */ + func(); + + /* Mark as done */ + *o = 1; + + return 0; + } + } + + YieldProcessor(); + + _ReadWriteBarrier(); + + state = *o; + } + + /* Done */ + return 0; +} + +static int pthread_mutex_lock(pthread_mutex_t *m) +{ + EnterCriticalSection(m); + return 0; +} + +static int pthread_mutex_unlock(pthread_mutex_t *m) +{ + LeaveCriticalSection(m); + return 0; +} + +static int pthread_mutex_trylock(pthread_mutex_t *m) +{ + return TryEnterCriticalSection(m) ? 0 : EBUSY; +} + +static int pthread_mutex_init(pthread_mutex_t *m, pthread_mutexattr_t *a) +{ + (void) a; + InitializeCriticalSection(m); + + return 0; +} + +static int pthread_mutex_destroy(pthread_mutex_t *m) +{ + DeleteCriticalSection(m); + return 0; +} + +#define pthread_mutex_getprioceiling(M, P) ENOTSUP +#define pthread_mutex_setprioceiling(M, P) ENOTSUP + +static int pthread_equal(pthread_t t1, pthread_t t2) +{ + return t1 == t2; +} + +static void pthread_testcancel(void); + +static int pthread_rwlock_init(pthread_rwlock_t *l, pthread_rwlockattr_t *a) +{ + (void) a; + InitializeSRWLock(l); + + return 0; +} + +static int pthread_rwlock_destroy(pthread_rwlock_t *l) +{ + (void) *l; + return 0; +} + +static int pthread_rwlock_rdlock(pthread_rwlock_t *l) +{ + pthread_testcancel(); + AcquireSRWLockShared(l); + + return 0; +} + +static int pthread_rwlock_wrlock(pthread_rwlock_t *l) +{ + pthread_testcancel(); + AcquireSRWLockExclusive(l); + + return 0; +} + +static void pthread_tls_init(void) +{ + _pthread_tls = TlsAlloc(); + + /* Cannot continue if out of indexes */ + if (_pthread_tls == TLS_OUT_OF_INDEXES) abort(); +} + +static void _pthread_cleanup_dest(pthread_t t) +{ + unsigned int i, j; + + for (j = 0; j < PTHREAD_DESTRUCTOR_ITERATIONS; j++) + { + int flag = 0; + + for (i = 0; i < t->keymax; i++) + { + void *val = t->keyval[i]; + + if (val) + { + pthread_rwlock_rdlock(&_pthread_key_lock); + if ((uintptr_t) _pthread_key_dest[i] > 1) + { + /* Call destructor */ + t->keyval[i] = NULL; + _pthread_key_dest[i](val); + flag = 1; + } + pthread_rwlock_unlock(&_pthread_key_lock); + } + } + + /* Nothing to do? */ + if (!flag) return; + } +} + +static pthread_t pthread_self(void) +{ + pthread_t t; + + _pthread_once_raw(&_pthread_tls_once, pthread_tls_init); + + t = (pthread_t)TlsGetValue(_pthread_tls); + + /* Main thread? */ + if (!t) + { + t = (pthread_t)malloc(sizeof(struct _pthread_v)); + + /* If cannot initialize main thread, then the only thing we can do is abort */ + if (!t) abort(); + + t->ret_arg = NULL; + t->func = NULL; + t->clean = NULL; + t->cancelled = 0; + t->p_state = PTHREAD_DEFAULT_ATTR; + t->keymax = 0; + t->keyval = NULL; + t->h = GetCurrentThread(); + + /* Save for later */ + TlsSetValue(_pthread_tls, t); + + if (setjmp(t->jb)) + { + /* Make sure we free ourselves if we are detached */ + if (!t->h) free(t); + + /* Time to die */ + _endthreadex(0); + } + } + + return t; +} + +static int pthread_rwlock_unlock(pthread_rwlock_t *l) +{ + void *state = *(void **)l; + + if (state == (void *) 1) + { + /* Known to be an exclusive lock */ + ReleaseSRWLockExclusive(l); + } + else + { + /* A shared unlock will work */ + ReleaseSRWLockShared(l); + } + + return 0; +} + + +static int pthread_rwlock_tryrdlock(pthread_rwlock_t *l) +{ + /* Get the current state of the lock */ + void *state = *(void **) l; + + if (!state) + { + /* Unlocked to locked */ + if (!_InterlockedCompareExchangePointer((volatile PVOID*) l, (void *)0x11, NULL)) return 0; + return EBUSY; + } + + /* A single writer exists */ + if (state == (void *) 1) return EBUSY; + + /* Multiple writers exist? */ + if ((uintptr_t) state & 14) return EBUSY; + + if (_InterlockedCompareExchangePointer((volatile PVOID*)l, (void *) ((uintptr_t)state + 16), state) == state) return 0; + + return EBUSY; +} + +static int pthread_rwlock_trywrlock(pthread_rwlock_t *l) +{ + /* Try to grab lock if it has no users */ + if (!_InterlockedCompareExchangePointer((volatile PVOID*)l, (void *)1, NULL)) return 0; + + return EBUSY; +} + +static unsigned long long _pthread_time_in_ms(void) +{ + struct __timeb64 tb; + + _ftime64(&tb); + + return tb.time * 1000 + tb.millitm; +} + +static unsigned long long _pthread_time_in_ms_from_timespec(const struct timespec *ts) +{ + unsigned long long t = ts->tv_sec * 1000; + t += ts->tv_nsec / 1000000; + + return t; +} + +static unsigned long long _pthread_rel_time_in_ms(const struct timespec *ts) +{ + unsigned long long t1 = _pthread_time_in_ms_from_timespec(ts); + unsigned long long t2 = _pthread_time_in_ms(); + + /* Prevent underflow */ + if (t1 < t2) return 0; + return t1 - t2; +} + +static int pthread_rwlock_timedrdlock(pthread_rwlock_t *l, const struct timespec *ts) +{ + unsigned long long ct = _pthread_time_in_ms(); + unsigned long long t = _pthread_time_in_ms_from_timespec(ts); + + pthread_testcancel(); + + /* Use a busy-loop */ + while (1) + { + /* Try to grab lock */ + if (!pthread_rwlock_tryrdlock(l)) return 0; + + /* Get current time */ + ct = _pthread_time_in_ms(); + + /* Have we waited long enough? */ + if (ct > t) return ETIMEDOUT; + } +} + +static int pthread_rwlock_timedwrlock(pthread_rwlock_t *l, const struct timespec *ts) +{ + unsigned long long ct = _pthread_time_in_ms(); + unsigned long long t = _pthread_time_in_ms_from_timespec(ts); + + pthread_testcancel(); + + /* Use a busy-loop */ + while (1) + { + /* Try to grab lock */ + if (!pthread_rwlock_trywrlock(l)) return 0; + + /* Get current time */ + ct = _pthread_time_in_ms(); + + /* Have we waited long enough? */ + if (ct > t) return ETIMEDOUT; + } +} + +static int pthread_get_concurrency(int *val) +{ + *val = _pthread_concur; + return 0; +} + +static int pthread_set_concurrency(int val) +{ + _pthread_concur = val; + return 0; +} + +#define pthread_getschedparam(T, P, S) ENOTSUP +#define pthread_setschedparam(T, P, S) ENOTSUP +#define pthread_getcpuclockid(T, C) ENOTSUP + +static int pthread_exit(void *res) +{ + pthread_t t = pthread_self(); + + t->ret_arg = res; + + _pthread_cleanup_dest(t); + + longjmp(t->jb, 1); +} + + +static void _pthread_invoke_cancel(void) +{ + _pthread_cleanup *pcup; + + _InterlockedDecrement(&_pthread_cancelling); + + /* Call cancel queue */ + for (pcup = pthread_self()->clean; pcup; pcup = pcup->next) + { + pcup->func(pcup->arg); + } + + pthread_exit(PTHREAD_CANCELED); +} + +static void pthread_testcancel(void) +{ + if (_pthread_cancelling) + { + pthread_t t = pthread_self(); + + if (t->cancelled && (t->p_state & PTHREAD_CANCEL_ENABLE)) + { + _pthread_invoke_cancel(); + } + } +} + + +static int pthread_cancel(pthread_t t) +{ + if (t->p_state & PTHREAD_CANCEL_ASYNCHRONOUS) + { + /* Dangerous asynchronous cancelling */ + CONTEXT ctxt; + + /* Already done? */ + if (t->cancelled) return ESRCH; + + ctxt.ContextFlags = CONTEXT_CONTROL; + + SuspendThread(t->h); + GetThreadContext(t->h, &ctxt); +#ifdef _M_X64 + ctxt.Rip = (uintptr_t) _pthread_invoke_cancel; +#else + ctxt.Eip = (uintptr_t) _pthread_invoke_cancel; +#endif + SetThreadContext(t->h, &ctxt); + + /* Also try deferred Cancelling */ + t->cancelled = 1; + + /* Notify everyone to look */ + _InterlockedIncrement(&_pthread_cancelling); + + ResumeThread(t->h); + } + else + { + /* Safe deferred Cancelling */ + t->cancelled = 1; + + /* Notify everyone to look */ + _InterlockedIncrement(&_pthread_cancelling); + } + + return 0; +} + +static unsigned _pthread_get_state(pthread_attr_t *attr, unsigned flag) +{ + return attr->p_state & flag; +} + +static int _pthread_set_state(pthread_attr_t *attr, unsigned flag, unsigned val) +{ + if (~flag & val) return EINVAL; + attr->p_state &= ~flag; + attr->p_state |= val; + + return 0; +} + +static int pthread_attr_init(pthread_attr_t *attr) +{ + attr->p_state = PTHREAD_DEFAULT_ATTR; + attr->stack = NULL; + attr->s_size = 0; + return 0; +} + +static int pthread_attr_destroy(pthread_attr_t *attr) +{ + /* No need to do anything */ + return 0; +} + + +static int pthread_attr_setdetachstate(pthread_attr_t *a, int flag) +{ + return _pthread_set_state(a, PTHREAD_CREATE_DETACHED, flag); +} + +static int pthread_attr_getdetachstate(pthread_attr_t *a, int *flag) +{ + *flag = _pthread_get_state(a, PTHREAD_CREATE_DETACHED); + return 0; +} + +static int pthread_attr_setinheritsched(pthread_attr_t *a, int flag) +{ + return _pthread_set_state(a, PTHREAD_INHERIT_SCHED, flag); +} + +static int pthread_attr_getinheritsched(pthread_attr_t *a, int *flag) +{ + *flag = _pthread_get_state(a, PTHREAD_INHERIT_SCHED); + return 0; +} + +static int pthread_attr_setscope(pthread_attr_t *a, int flag) +{ + return _pthread_set_state(a, PTHREAD_SCOPE_SYSTEM, flag); +} + +static int pthread_attr_getscope(pthread_attr_t *a, int *flag) +{ + *flag = _pthread_get_state(a, PTHREAD_SCOPE_SYSTEM); + return 0; +} + +static int pthread_attr_getstackaddr(pthread_attr_t *attr, void **stack) +{ + *stack = attr->stack; + return 0; +} + +static int pthread_attr_setstackaddr(pthread_attr_t *attr, void *stack) +{ + attr->stack = stack; + return 0; +} + +static int pthread_attr_getstacksize(pthread_attr_t *attr, size_t *size) +{ + *size = attr->s_size; + return 0; +} + +static int pthread_attr_setstacksize(pthread_attr_t *attr, size_t size) +{ + attr->s_size = size; + return 0; +} + +#define pthread_attr_getguardsize(A, S) ENOTSUP +#define pthread_attr_setgaurdsize(A, S) ENOTSUP +#define pthread_attr_getschedparam(A, S) ENOTSUP +#define pthread_attr_setschedparam(A, S) ENOTSUP +#define pthread_attr_getschedpolicy(A, S) ENOTSUP +#define pthread_attr_setschedpolicy(A, S) ENOTSUP + + +static int pthread_setcancelstate(int state, int *oldstate) +{ + pthread_t t = pthread_self(); + + if ((state & PTHREAD_CANCEL_ENABLE) != state) return EINVAL; + if (oldstate) *oldstate = t->p_state & PTHREAD_CANCEL_ENABLE; + t->p_state &= ~PTHREAD_CANCEL_ENABLE; + t->p_state |= state; + + return 0; +} + +static int pthread_setcanceltype(int type, int *oldtype) +{ + pthread_t t = pthread_self(); + + if ((type & PTHREAD_CANCEL_ASYNCHRONOUS) != type) return EINVAL; + if (oldtype) *oldtype = t->p_state & PTHREAD_CANCEL_ASYNCHRONOUS; + t->p_state &= ~PTHREAD_CANCEL_ASYNCHRONOUS; + t->p_state |= type; + + return 0; +} + +static unsigned int __stdcall pthread_create_wrapper(void *args) +{ + struct _pthread_v *tv = (struct _pthread_v*)args; + + _pthread_once_raw(&_pthread_tls_once, pthread_tls_init); + + TlsSetValue(_pthread_tls, tv); + + if (!setjmp(tv->jb)) + { + /* Call function and save return value */ + tv->ret_arg = tv->func(tv->ret_arg); + + /* Clean up destructors */ + _pthread_cleanup_dest(tv); + } + + /* If we exit too early, then we can race with create */ + while (tv->h == (HANDLE) -1) + { + YieldProcessor(); + _ReadWriteBarrier(); + } + + /* Make sure we free ourselves if we are detached */ + if (!tv->h) free(tv); + + return 0; +} + +static int pthread_create(pthread_t *th, pthread_attr_t *attr, void *(* func)(void *), void *arg) +{ + struct _pthread_v *tv = (struct _pthread_v*)malloc(sizeof(struct _pthread_v)); + unsigned ssize = 0; + + if (!tv) return 1; + + *th = tv; + + /* Save data in pthread_t */ + tv->ret_arg = arg; + tv->func = func; + tv->clean = NULL; + tv->cancelled = 0; + tv->p_state = PTHREAD_DEFAULT_ATTR; + tv->keymax = 0; + tv->keyval = NULL; + tv->h = (HANDLE) -1; + + if (attr) + { + tv->p_state = attr->p_state; + ssize = (unsigned)attr->s_size; + } + + /* Make sure tv->h has value of -1 */ + _ReadWriteBarrier(); + + tv->h = (HANDLE) _beginthreadex(NULL, ssize, pthread_create_wrapper, tv, 0, NULL); + + /* Failed */ + if (!tv->h) return 1; + + if (tv->p_state & PTHREAD_CREATE_DETACHED) + { + CloseHandle(tv->h); + _ReadWriteBarrier(); + tv->h = 0; + } + + return 0; +} + +static int pthread_join(pthread_t t, void **res) +{ + struct _pthread_v *tv = t; + + pthread_testcancel(); + + WaitForSingleObject(tv->h, INFINITE); + CloseHandle(tv->h); + + /* Obtain return value */ + if (res) *res = tv->ret_arg; + + free(tv); + + return 0; +} + +static int pthread_detach(pthread_t t) +{ + struct _pthread_v *tv = t; + + /* + * This can't race with thread exit because + * our call would be undefined if called on a dead thread. + */ + + CloseHandle(tv->h); + _ReadWriteBarrier(); + tv->h = 0; + + return 0; +} + +static int pthread_mutexattr_init(pthread_mutexattr_t *a) +{ + *a = 0; + return 0; +} + +static int pthread_mutexattr_destroy(pthread_mutexattr_t *a) +{ + (void) a; + return 0; +} + +static int pthread_mutexattr_gettype(pthread_mutexattr_t *a, int *type) +{ + *type = *a & 3; + + return 0; +} + +static int pthread_mutexattr_settype(pthread_mutexattr_t *a, int type) +{ + if ((unsigned) type > 3) return EINVAL; + *a &= ~3; + *a |= type; + + return 0; +} + +static int pthread_mutexattr_getpshared(pthread_mutexattr_t *a, int *type) +{ + *type = *a & 4; + + return 0; +} + +static int pthread_mutexattr_setpshared(pthread_mutexattr_t * a, int type) +{ + if ((type & 4) != type) return EINVAL; + + *a &= ~4; + *a |= type; + + return 0; +} + +static int pthread_mutexattr_getprotocol(pthread_mutexattr_t *a, int *type) +{ + *type = *a & (8 + 16); + + return 0; +} + +static int pthread_mutexattr_setprotocol(pthread_mutexattr_t *a, int type) +{ + if ((type & (8 + 16)) != 8 + 16) return EINVAL; + + *a &= ~(8 + 16); + *a |= type; + + return 0; +} + +static int pthread_mutexattr_getprioceiling(pthread_mutexattr_t *a, int * prio) +{ + *prio = *a / PTHREAD_PRIO_MULT; + return 0; +} + +static int pthread_mutexattr_setprioceiling(pthread_mutexattr_t *a, int prio) +{ + *a &= (PTHREAD_PRIO_MULT - 1); + *a += prio * PTHREAD_PRIO_MULT; + + return 0; +} + +static int pthread_mutex_timedlock(pthread_mutex_t *m, struct timespec *ts) +{ + unsigned long long t, ct; + + struct _pthread_crit_t + { + void *debug; + LONG count; + LONG r_count; + HANDLE owner; + HANDLE sem; + ULONG_PTR spin; + }; + + /* Try to lock it without waiting */ + if (!pthread_mutex_trylock(m)) return 0; + + ct = _pthread_time_in_ms(); + t = _pthread_time_in_ms_from_timespec(ts); + + while (1) + { + /* Have we waited long enough? */ + if (ct > t) return ETIMEDOUT; + + /* Wait on semaphore within critical section */ + WaitForSingleObject(((struct _pthread_crit_t *)m)->sem, (DWORD)(t - ct)); + + /* Try to grab lock */ + if (!pthread_mutex_trylock(m)) return 0; + + /* Get current time */ + ct = _pthread_time_in_ms(); + } +} + +#define _PTHREAD_BARRIER_FLAG (1<<30) + +static int pthread_barrier_destroy(pthread_barrier_t *b) +{ + EnterCriticalSection(&b->m); + + while (b->total > _PTHREAD_BARRIER_FLAG) + { + /* Wait until everyone exits the barrier */ + SleepConditionVariableCS(&b->cv, &b->m, INFINITE); + } + + LeaveCriticalSection(&b->m); + + DeleteCriticalSection(&b->m); + + return 0; +} + +static int pthread_barrier_init(pthread_barrier_t *b, void *attr, int count) +{ + /* Ignore attr */ + (void) attr; + + b->count = count; + b->total = 0; + + InitializeCriticalSection(&b->m); + InitializeConditionVariable(&b->cv); + + return 0; +} + +static int pthread_barrier_wait(pthread_barrier_t *b) +{ + EnterCriticalSection(&b->m); + + while (b->total > _PTHREAD_BARRIER_FLAG) + { + /* Wait until everyone exits the barrier */ + SleepConditionVariableCS(&b->cv, &b->m, INFINITE); + } + + /* Are we the first to enter? */ + if (b->total == _PTHREAD_BARRIER_FLAG) b->total = 0; + + b->total++; + + if (b->total == b->count) + { + b->total += _PTHREAD_BARRIER_FLAG - 1; + WakeAllConditionVariable(&b->cv); + + LeaveCriticalSection(&b->m); + + return 1; + } + else + { + while (b->total < _PTHREAD_BARRIER_FLAG) + { + /* Wait until enough threads enter the barrier */ + SleepConditionVariableCS(&b->cv, &b->m, INFINITE); + } + + b->total--; + + /* Get entering threads to wake up */ + if (b->total == _PTHREAD_BARRIER_FLAG) WakeAllConditionVariable(&b->cv); + + LeaveCriticalSection(&b->m); + + return 0; + } +} + +static int pthread_barrierattr_init(void **attr) +{ + *attr = NULL; + return 0; +} + +static int pthread_barrierattr_destroy(void **attr) +{ + /* Ignore attr */ + (void) attr; + + return 0; +} + +static int pthread_barrierattr_setpshared(void **attr, int s) +{ + INT_PTR ip = s; + *attr = (void *) ip; + return 0; +} + +static int pthread_barrierattr_getpshared(void **attr, int *s) +{ + *s = (int) (size_t) *attr; + + return 0; +} + +static int pthread_key_create(pthread_key_t *key, void (* dest)(void *)) +{ + unsigned int i; + long nmax; + void (**d)(void *); + + if (!key) return EINVAL; + + pthread_rwlock_wrlock(&_pthread_key_lock); + + for (i = _pthread_key_sch; i < _pthread_key_max; i++) + { + if (!_pthread_key_dest[i]) + { + *key = i; + if (dest) + { + _pthread_key_dest[i] = dest; + } + else + { + _pthread_key_dest[i] = (void(*)(void *))1; + } + pthread_rwlock_unlock(&_pthread_key_lock); + + return 0; + } + } + + for (i = 0; i < _pthread_key_sch; i++) + { + if (!_pthread_key_dest[i]) + { + *key = i; + if (dest) + { + _pthread_key_dest[i] = dest; + } + else + { + _pthread_key_dest[i] = (void(*)(void *))1; + } + pthread_rwlock_unlock(&_pthread_key_lock); + + return 0; + } + } + + if (!_pthread_key_max) _pthread_key_max = 1; + if (_pthread_key_max == PTHREAD_KEYS_MAX) + { + pthread_rwlock_unlock(&_pthread_key_lock); + + return ENOMEM; + } + + nmax = _pthread_key_max * 2; + if (nmax > PTHREAD_KEYS_MAX) nmax = PTHREAD_KEYS_MAX; + + /* No spare room anywhere */ + d = (void(**)(void*))realloc(_pthread_key_dest, nmax * sizeof(*d)); + if (!d) + { + pthread_rwlock_unlock(&_pthread_key_lock); + + return ENOMEM; + } + + /* Clear new region */ + memset((void *) &d[_pthread_key_max], 0, (nmax-_pthread_key_max)*sizeof(void *)); + + /* Use new region */ + _pthread_key_dest = d; + _pthread_key_sch = _pthread_key_max + 1; + *key = _pthread_key_max; + _pthread_key_max = nmax; + + if (dest) + { + _pthread_key_dest[*key] = dest; + } + else + { + _pthread_key_dest[*key] = (void(*)(void *))1; + } + + pthread_rwlock_unlock(&_pthread_key_lock); + + return 0; +} + +static int pthread_key_delete(pthread_key_t key) +{ + if (key > _pthread_key_max) return EINVAL; + if (!_pthread_key_dest) return EINVAL; + + pthread_rwlock_wrlock(&_pthread_key_lock); + _pthread_key_dest[key] = NULL; + + /* Start next search from our location */ + if (_pthread_key_sch > key) _pthread_key_sch = key; + + pthread_rwlock_unlock(&_pthread_key_lock); + + return 0; +} + +static void *pthread_getspecific(pthread_key_t key) +{ + pthread_t t = pthread_self(); + + if (key >= t->keymax) return NULL; + + return t->keyval[key]; + +} + +static int pthread_setspecific(pthread_key_t key, const void *value) +{ + pthread_t t = pthread_self(); + + if (key > t->keymax) + { + int keymax = (key + 1) * 2; + void **kv = (void**)realloc(t->keyval, keymax * sizeof(void *)); + + if (!kv) return ENOMEM; + + /* Clear new region */ + memset(&kv[t->keymax], 0, (keymax - t->keymax)*sizeof(void*)); + + t->keyval = kv; + t->keymax = keymax; + } + + t->keyval[key] = (void *) value; + + return 0; +} + + +static int pthread_spin_init(pthread_spinlock_t *l, int pshared) +{ + (void) pshared; + + *l = 0; + return 0; +} + +static int pthread_spin_destroy(pthread_spinlock_t *l) +{ + (void) l; + return 0; +} + +/* No-fair spinlock due to lack of knowledge of thread number */ +static int pthread_spin_lock(pthread_spinlock_t *l) +{ + while (_InterlockedExchange(l, EBUSY)) + { + /* Don't lock the bus whilst waiting */ + while (*l) + { + YieldProcessor(); + + /* Compiler barrier. Prevent caching of *l */ + _ReadWriteBarrier(); + } + } + + return 0; +} + +static int pthread_spin_trylock(pthread_spinlock_t *l) +{ + return _InterlockedExchange(l, EBUSY); +} + +static int pthread_spin_unlock(pthread_spinlock_t *l) +{ + /* Compiler barrier. The store below acts with release symmantics */ + _ReadWriteBarrier(); + + *l = 0; + + return 0; +} + +static int pthread_cond_init(pthread_cond_t *c, pthread_condattr_t *a) +{ + (void) a; + + InitializeConditionVariable(c); + return 0; +} + +static int pthread_cond_signal(pthread_cond_t *c) +{ + WakeConditionVariable(c); + return 0; +} + +static int pthread_cond_broadcast(pthread_cond_t *c) +{ + WakeAllConditionVariable(c); + return 0; +} + +static int pthread_cond_wait(pthread_cond_t *c, pthread_mutex_t *m) +{ + pthread_testcancel(); + SleepConditionVariableCS(c, m, INFINITE); + return 0; +} + +static int pthread_cond_destroy(pthread_cond_t *c) +{ + (void) c; + return 0; +} + +static int pthread_cond_timedwait(pthread_cond_t *c, pthread_mutex_t *m, struct timespec *t) +{ + unsigned long long tm = _pthread_rel_time_in_ms(t); + + pthread_testcancel(); + + if (!SleepConditionVariableCS(c, m, (DWORD)(tm))) return ETIMEDOUT; + + /* We can have a spurious wakeup after the timeout */ + if (!_pthread_rel_time_in_ms(t)) return ETIMEDOUT; + + return 0; +} + +static int pthread_condattr_destroy(pthread_condattr_t *a) +{ + (void) a; + return 0; +} + +#define pthread_condattr_getclock(A, C) ENOTSUP +#define pthread_condattr_setclock(A, C) ENOTSUP + +static int pthread_condattr_init(pthread_condattr_t *a) +{ + *a = 0; + return 0; +} + +static int pthread_condattr_getpshared(pthread_condattr_t *a, int *s) +{ + *s = *a; + return 0; +} + +static int pthread_condattr_setpshared(pthread_condattr_t *a, int s) +{ + *a = s; + return 0; +} + +static int pthread_rwlockattr_destroy(pthread_rwlockattr_t *a) +{ + (void) a; + return 0; +} + +static int pthread_rwlockattr_init(pthread_rwlockattr_t *a) +{ + *a = 0; +} + +static int pthread_rwlockattr_getpshared(pthread_rwlockattr_t *a, int *s) +{ + *s = *a; + return 0; +} + +static int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *a, int s) +{ + *a = s; + return 0; +} + + +/* No fork() in windows - so ignore this */ +#define pthread_atfork(F1,F2,F3) 0 + +/* Windows has rudimentary signals support */ +#define pthread_kill(T, S) 0 +#define pthread_sigmask(H, S1, S2) 0 + + +/* Wrap cancellation points */ +#define accept(...) (pthread_testcancel(), accept(__VA_ARGS__)) +#define aio_suspend(...) (pthread_testcancel(), aio_suspend(__VA_ARGS__)) +#define clock_nanosleep(...) (pthread_testcancel(), clock_nanosleep(__VA_ARGS__)) +#define close(...) (pthread_testcancel(), close(__VA_ARGS__)) +#define connect(...) (pthread_testcancel(), connect(__VA_ARGS__)) +#define creat(...) (pthread_testcancel(), creat(__VA_ARGS__)) +#define fcntl(...) (pthread_testcancel(), fcntl(__VA_ARGS__)) +#define fdatasync(...) (pthread_testcancel(), fdatasync(__VA_ARGS__)) +#define fsync(...) (pthread_testcancel(), fsync(__VA_ARGS__)) +#define getmsg(...) (pthread_testcancel(), getmsg(__VA_ARGS__)) +#define getpmsg(...) (pthread_testcancel(), getpmsg(__VA_ARGS__)) +#define lockf(...) (pthread_testcancel(), lockf(__VA_ARGS__)) +#define mg_receive(...) (pthread_testcancel(), mg_receive(__VA_ARGS__)) +#define mg_send(...) (pthread_testcancel(), mg_send(__VA_ARGS__)) +#define mg_timedreceive(...) (pthread_testcancel(), mg_timedreceive(__VA_ARGS__)) +#define mg_timessend(...) (pthread_testcancel(), mg_timedsend(__VA_ARGS__)) +#define msgrcv(...) (pthread_testcancel(), msgrecv(__VA_ARGS__)) +#define msgsnd(...) (pthread_testcancel(), msgsnd(__VA_ARGS__)) +#define msync(...) (pthread_testcancel(), msync(__VA_ARGS__)) +#define nanosleep(...) (pthread_testcancel(), nanosleep(__VA_ARGS__)) +#define open(...) (pthread_testcancel(), open(__VA_ARGS__)) +#define pause(...) (pthread_testcancel(), pause(__VA_ARGS__)) +#define poll(...) (pthread_testcancel(), poll(__VA_ARGS__)) +#define pread(...) (pthread_testcancel(), pread(__VA_ARGS__)) +#define pselect(...) (pthread_testcancel(), pselect(__VA_ARGS__)) +#define putmsg(...) (pthread_testcancel(), putmsg(__VA_ARGS__)) +#define putpmsg(...) (pthread_testcancel(), putpmsg(__VA_ARGS__)) +#define pwrite(...) (pthread_testcancel(), pwrite(__VA_ARGS__)) +#define read(...) (pthread_testcancel(), read(__VA_ARGS__)) +#define readv(...) (pthread_testcancel(), readv(__VA_ARGS__)) +#define recv(...) (pthread_testcancel(), recv(__VA_ARGS__)) +#define recvfrom(...) (pthread_testcancel(), recvfrom(__VA_ARGS__)) +#define recvmsg(...) (pthread_testcancel(), recvmsg(__VA_ARGS__)) +#define select(...) (pthread_testcancel(), select(__VA_ARGS__)) +#define sem_timedwait(...) (pthread_testcancel(), sem_timedwait(__VA_ARGS__)) +#define sem_wait(...) (pthread_testcancel(), sem_wait(__VA_ARGS__)) +#define send(...) (pthread_testcancel(), send(__VA_ARGS__)) +#define sendmsg(...) (pthread_testcancel(), sendmsg(__VA_ARGS__)) +#define sendto(...) (pthread_testcancel(), sendto(__VA_ARGS__)) +#define sigpause(...) (pthread_testcancel(), sigpause(__VA_ARGS__)) +#define sigsuspend(...) (pthread_testcancel(), sigsuspend(__VA_ARGS__)) +#define sigwait(...) (pthread_testcancel(), sigwait(__VA_ARGS__)) +#define sigwaitinfo(...) (pthread_testcancel(), sigwaitinfo(__VA_ARGS__)) +//#define sleep(...) (pthread_testcancel(), sleep(__VA_ARGS__)) +//#define Sleep(...) (pthread_testcancel(), Sleep(__VA_ARGS__)) +#define system(...) (pthread_testcancel(), system(__VA_ARGS__)) + + +#define access(...) (pthread_testcancel(), access(__VA_ARGS__)) +#define asctime(...) (pthread_testcancel(), asctime(__VA_ARGS__)) +#define asctime_r(...) (pthread_testcancel(), asctime_r(__VA_ARGS__)) +#define catclose(...) (pthread_testcancel(), catclose(__VA_ARGS__)) +#define catgets(...) (pthread_testcancel(), catgets(__VA_ARGS__)) +#define catopen(...) (pthread_testcancel(), catopen(__VA_ARGS__)) +#define closedir(...) (pthread_testcancel(), closedir(__VA_ARGS__)) +#define closelog(...) (pthread_testcancel(), closelog(__VA_ARGS__)) +#define ctermid(...) (pthread_testcancel(), ctermid(__VA_ARGS__)) +#define ctime(...) (pthread_testcancel(), ctime(__VA_ARGS__)) +#define ctime_r(...) (pthread_testcancel(), ctime_r(__VA_ARGS__)) +#define dbm_close(...) (pthread_testcancel(), dbm_close(__VA_ARGS__)) +#define dbm_delete(...) (pthread_testcancel(), dbm_delete(__VA_ARGS__)) +#define dbm_fetch(...) (pthread_testcancel(), dbm_fetch(__VA_ARGS__)) +#define dbm_nextkey(...) (pthread_testcancel(), dbm_nextkey(__VA_ARGS__)) +#define dbm_open(...) (pthread_testcancel(), dbm_open(__VA_ARGS__)) +#define dbm_store(...) (pthread_testcancel(), dbm_store(__VA_ARGS__)) +#define dlclose(...) (pthread_testcancel(), dlclose(__VA_ARGS__)) +#define dlopen(...) (pthread_testcancel(), dlopen(__VA_ARGS__)) +#define endgrent(...) (pthread_testcancel(), endgrent(__VA_ARGS__)) +#define endhostent(...) (pthread_testcancel(), endhostent(__VA_ARGS__)) +#define endnetent(...) (pthread_testcancel(), endnetent(__VA_ARGS__)) +#define endprotoent(...) (pthread_testcancel(), endprotoend(__VA_ARGS__)) +#define endpwent(...) (pthread_testcancel(), endpwent(__VA_ARGS__)) +#define endservent(...) (pthread_testcancel(), endservent(__VA_ARGS__)) +#define endutxent(...) (pthread_testcancel(), endutxent(__VA_ARGS__)) +#define fclose(...) (pthread_testcancel(), fclose(__VA_ARGS__)) +#define fflush(...) (pthread_testcancel(), fflush(__VA_ARGS__)) +#define fgetc(...) (pthread_testcancel(), fgetc(__VA_ARGS__)) +#define fgetpos(...) (pthread_testcancel(), fgetpos(__VA_ARGS__)) +#define fgets(...) (pthread_testcancel(), fgets(__VA_ARGS__)) +#define fgetwc(...) (pthread_testcancel(), fgetwc(__VA_ARGS__)) +#define fgetws(...) (pthread_testcancel(), fgetws(__VA_ARGS__)) +#define fmtmsg(...) (pthread_testcancel(), fmtmsg(__VA_ARGS__)) +#define fopen(...) (pthread_testcancel(), fopen(__VA_ARGS__)) +#define fpathconf(...) (pthread_testcancel(), fpathconf(__VA_ARGS__)) +#define fprintf(...) (pthread_testcancel(), fprintf(__VA_ARGS__)) +#define fputc(...) (pthread_testcancel(), fputc(__VA_ARGS__)) +#define fputs(...) (pthread_testcancel(), fputs(__VA_ARGS__)) +#define fputwc(...) (pthread_testcancel(), fputwc(__VA_ARGS__)) +#define fputws(...) (pthread_testcancel(), fputws(__VA_ARGS__)) +#define fread(...) (pthread_testcancel(), fread(__VA_ARGS__)) +#define freopen(...) (pthread_testcancel(), freopen(__VA_ARGS__)) +#define fscanf(...) (pthread_testcancel(), fscanf(__VA_ARGS__)) +#define fseek(...) (pthread_testcancel(), fseek(__VA_ARGS__)) +#define fseeko(...) (pthread_testcancel(), fseeko(__VA_ARGS__)) +#define fsetpos(...) (pthread_testcancel(), fsetpos(__VA_ARGS__)) +#define fstat(...) (pthread_testcancel(), fstat(__VA_ARGS__)) +#define ftell(...) (pthread_testcancel(), ftell(__VA_ARGS__)) +#define ftello(...) (pthread_testcancel(), ftello(__VA_ARGS__)) +#define ftw(...) (pthread_testcancel(), ftw(__VA_ARGS__)) +#define fwprintf(...) (pthread_testcancel(), fwprintf(__VA_ARGS__)) +#define fwrite(...) (pthread_testcancel(), fwrite(__VA_ARGS__)) +#define fwscanf(...) (pthread_testcancel(), fwscanf(__VA_ARGS__)) +#define getaddrinfo(...) (pthread_testcancel(), getaddrinfo(__VA_ARGS__)) +#define getc(...) (pthread_testcancel(), getc(__VA_ARGS__)) +#define getc_unlocked(...) (pthread_testcancel(), getc_unlocked(__VA_ARGS__)) +#define getchar(...) (pthread_testcancel(), getchar(__VA_ARGS__)) +#define getchar_unlocked(...) (pthread_testcancel(), getchar_unlocked(__VA_ARGS__)) +#define getcwd(...) (pthread_testcancel(), getcwd(__VA_ARGS__)) +#define getdate(...) (pthread_testcancel(), getdate(__VA_ARGS__)) +#define getgrent(...) (pthread_testcancel(), getgrent(__VA_ARGS__)) +#define getgrgid(...) (pthread_testcancel(), getgrgid(__VA_ARGS__)) +#define getgrgid_r(...) (pthread_testcancel(), getgrgid_r(__VA_ARGS__)) +#define gergrnam(...) (pthread_testcancel(), getgrnam(__VA_ARGS__)) +#define getgrnam_r(...) (pthread_testcancel(), getgrnam_r(__VA_ARGS__)) +#define gethostbyaddr(...) (pthread_testcancel(), gethostbyaddr(__VA_ARGS__)) +#define gethostbyname(...) (pthread_testcancel(), gethostbyname(__VA_ARGS__)) +#define gethostent(...) (pthread_testcancel(), gethostent(__VA_ARGS__)) +#define gethostid(...) (pthread_testcancel(), gethostid(__VA_ARGS__)) +#define gethostname(...) (pthread_testcancel(), gethostname(__VA_ARGS__)) +#define getlogin(...) (pthread_testcancel(), getlogin(__VA_ARGS__)) +#define getlogin_r(...) (pthread_testcancel(), getlogin_r(__VA_ARGS__)) +#define getnameinfo(...) (pthread_testcancel(), getnameinfo(__VA_ARGS__)) +#define getnetbyaddr(...) (pthread_testcancel(), getnetbyaddr(__VA_ARGS__)) +#define getnetbyname(...) (pthread_testcancel(), getnetbyname(__VA_ARGS__)) +#define getnetent(...) (pthread_testcancel(), getnetent(__VA_ARGS__)) +#define getopt(...) (pthread_testcancel(), getopt(__VA_ARGS__)) +#define getprotobyname(...) (pthread_testcancel(), getprotobyname(__VA_ARGS__)) +#define getprotobynumber(...) (pthread_testcancel(), getprotobynumber(__VA_ARGS__)) +#define getprotoent(...) (pthread_testcancel(), getprotoent(__VA_ARGS__)) +#define getpwent(...) (pthread_testcancel(), getpwent(__VA_ARGS__)) +#define getpwnam(...) (pthread_testcancel(), getpwnam(__VA_ARGS__)) +#define getpwnam_r(...) (pthread_testcancel(), getpwnam_r(__VA_ARGS__)) +#define getpwuid(...) (pthread_testcancel(), getpwuid(__VA_ARGS__)) +#define getpwuid_r(...) (pthread_testcancel(), getpwuid_r(__VA_ARGS__)) +#define gets(...) (pthread_testcancel(), gets(__VA_ARGS__)) +#define getservbyname(...) (pthread_testcancel(), getservbyname(__VA_ARGS__)) +#define getservbyport(...) (pthread_testcancel(), getservbyport(__VA_ARGS__)) +#define getservent(...) (pthread_testcancel(), getservent(__VA_ARGS__)) +#define getutxent(...) (pthread_testcancel(), getutxent(__VA_ARGS__)) +#define getutxid(...) (pthread_testcancel(), getutxid(__VA_ARGS__)) +#define getutxline(...) (pthread_testcancel(), getutxline(__VA_ARGS__)) +#undef getwc +#define getwc(...) (pthread_testcancel(), getwc(__VA_ARGS__)) +#undef getwchar +#define getwchar(...) (pthread_testcancel(), getwchar(__VA_ARGS__)) +#define getwd(...) (pthread_testcancel(), getwd(__VA_ARGS__)) +#define glob(...) (pthread_testcancel(), glob(__VA_ARGS__)) +#define iconv_close(...) (pthread_testcancel(), iconv_close(__VA_ARGS__)) +#define iconv_open(...) (pthread_testcancel(), iconv_open(__VA_ARGS__)) +#define ioctl(...) (pthread_testcancel(), ioctl(__VA_ARGS__)) +#define link(...) (pthread_testcancel(), link(__VA_ARGS__)) +#define localtime(...) (pthread_testcancel(), localtime(__VA_ARGS__)) +#define localtime_r(...) (pthread_testcancel(), localtime_r(__VA_ARGS__)) +#define lseek(...) (pthread_testcancel(), lseek(__VA_ARGS__)) +#define lstat(...) (pthread_testcancel(), lstat(__VA_ARGS__)) +#define mkstemp(...) (pthread_testcancel(), mkstemp(__VA_ARGS__)) +#define nftw(...) (pthread_testcancel(), nftw(__VA_ARGS__)) +#define opendir(...) (pthread_testcancel(), opendir(__VA_ARGS__)) +#define openlog(...) (pthread_testcancel(), openlog(__VA_ARGS__)) +#define pathconf(...) (pthread_testcancel(), pathconf(__VA_ARGS__)) +#define pclose(...) (pthread_testcancel(), pclose(__VA_ARGS__)) +#define perror(...) (pthread_testcancel(), perror(__VA_ARGS__)) +#define popen(...) (pthread_testcancel(), popen(__VA_ARGS__)) +#define posix_fadvise(...) (pthread_testcancel(), posix_fadvise(__VA_ARGS__)) +#define posix_fallocate(...) (pthread_testcancel(), posix_fallocate(__VA_ARGS__)) +#define posix_madvise(...) (pthread_testcancel(), posix_madvise(__VA_ARGS__)) +#define posix_openpt(...) (pthread_testcancel(), posix_openpt(__VA_ARGS__)) +#define posix_spawn(...) (pthread_testcancel(), posix_spawn(__VA_ARGS__)) +#define posix_spawnp(...) (pthread_testcancel(), posix_spawnp(__VA_ARGS__)) +#define posix_trace_clear(...) (pthread_testcancel(), posix_trace_clear(__VA_ARGS__)) +#define posix_trace_close(...) (pthread_testcancel(), posix_trace_close(__VA_ARGS__)) +#define posix_trace_create(...) (pthread_testcancel(), posix_trace_create(__VA_ARGS__)) +#define posix_trace_create_withlog(...) (pthread_testcancel(), posix_trace_create_withlog(__VA_ARGS__)) +#define posix_trace_eventtypelist_getne(...) (pthread_testcancel(), posix_trace_eventtypelist_getne(__VA_ARGS__)) +#define posix_trace_eventtypelist_rewin(...) (pthread_testcancel(), posix_trace_eventtypelist_rewin(__VA_ARGS__)) +#define posix_trace_flush(...) (pthread_testcancel(), posix_trace_flush(__VA_ARGS__)) +#define posix_trace_get_attr(...) (pthread_testcancel(), posix_trace_get_attr(__VA_ARGS__)) +#define posix_trace_get_filter(...) (pthread_testcancel(), posix_trace_get_filter(__VA_ARGS__)) +#define posix_trace_get_status(...) (pthread_testcancel(), posix_trace_get_status(__VA_ARGS__)) +#define posix_trace_getnext_event(...) (pthread_testcancel(), posix_trace_getnext_event(__VA_ARGS__)) +#define posix_trace_open(...) (pthread_testcancel(), posix_trace_open(__VA_ARGS__)) +#define posix_trace_rewind(...) (pthread_testcancel(), posix_trace_rewind(__VA_ARGS__)) +#define posix_trace_setfilter(...) (pthread_testcancel(), posix_trace_setfilter(__VA_ARGS__)) +#define posix_trace_shutdown(...) (pthread_testcancel(), posix_trace_shutdown(__VA_ARGS__)) +#define posix_trace_timedgetnext_event(...) (pthread_testcancel(), posix_trace_timedgetnext_event(__VA_ARGS__)) +#define posix_typed_mem_open(...) (pthread_testcancel(), posix_typed_mem_open(__VA_ARGS__)) +#define printf(...) (pthread_testcancel(), printf(__VA_ARGS__)) +#define putc(...) (pthread_testcancel(), putc(__VA_ARGS__)) +#define putc_unlocked(...) (pthread_testcancel(), putc_unlocked(__VA_ARGS__)) +#define putchar(...) (pthread_testcancel(), putchar(__VA_ARGS__)) +#define putchar_unlocked(...) (pthread_testcancel(), putchar_unlocked(__VA_ARGS__)) +#define puts(...) (pthread_testcancel(), puts(__VA_ARGS__)) +#define pututxline(...) (pthread_testcancel(), pututxline(__VA_ARGS__)) +#undef putwc +#define putwc(...) (pthread_testcancel(), putwc(__VA_ARGS__)) +#undef putwchar +#define putwchar(...) (pthread_testcancel(), putwchar(__VA_ARGS__)) +#define readdir(...) (pthread_testcancel(), readdir(__VA_ARSG__)) +#define readdir_r(...) (pthread_testcancel(), readdir_r(__VA_ARGS__)) +#define remove(...) (pthread_testcancel(), remove(__VA_ARGS__)) +#define rename(...) (pthread_testcancel(), rename(__VA_ARGS__)) +#define rewind(...) (pthread_testcancel(), rewind(__VA_ARGS__)) +#define rewinddir(...) (pthread_testcancel(), rewinddir(__VA_ARGS__)) +#define scanf(...) (pthread_testcancel(), scanf(__VA_ARGS__)) +#define seekdir(...) (pthread_testcancel(), seekdir(__VA_ARGS__)) +#define semop(...) (pthread_testcancel(), semop(__VA_ARGS__)) +#define setgrent(...) (pthread_testcancel(), setgrent(__VA_ARGS__)) +#define sethostent(...) (pthread_testcancel(), sethostemt(__VA_ARGS__)) +#define setnetent(...) (pthread_testcancel(), setnetent(__VA_ARGS__)) +#define setprotoent(...) (pthread_testcancel(), setprotoent(__VA_ARGS__)) +#define setpwent(...) (pthread_testcancel(), setpwent(__VA_ARGS__)) +#define setservent(...) (pthread_testcancel(), setservent(__VA_ARGS__)) +#define setutxent(...) (pthread_testcancel(), setutxent(__VA_ARGS__)) +#define stat(...) (pthread_testcancel(), stat(__VA_ARGS__)) +#define strerror(...) (pthread_testcancel(), strerror(__VA_ARGS__)) +#define strerror_r(...) (pthread_testcancel(), strerror_r(__VA_ARGS__)) +#define strftime(...) (pthread_testcancel(), strftime(__VA_ARGS__)) +#define symlink(...) (pthread_testcancel(), symlink(__VA_ARGS__)) +#define sync(...) (pthread_testcancel(), sync(__VA_ARGS__)) +#define syslog(...) (pthread_testcancel(), syslog(__VA_ARGS__)) +#define tmpfile(...) (pthread_testcancel(), tmpfile(__VA_ARGS__)) +#define tmpnam(...) (pthread_testcancel(), tmpnam(__VA_ARGS__)) +#define ttyname(...) (pthread_testcancel(), ttyname(__VA_ARGS__)) +#define ttyname_r(...) (pthread_testcancel(), ttyname_r(__VA_ARGS__)) +#define tzset(...) (pthread_testcancel(), tzset(__VA_ARGS__)) +#define ungetc(...) (pthread_testcancel(), ungetc(__VA_ARGS__)) +#define ungetwc(...) (pthread_testcancel(), ungetwc(__VA_ARGS__)) +#define unlink(...) (pthread_testcancel(), unlink(__VA_ARGS__)) +#define vfprintf(...) (pthread_testcancel(), vfprintf(__VA_ARGS__)) +#define vfwprintf(...) (pthread_testcancel(), vfwprintf(__VA_ARGS__)) +#define vprintf(...) (pthread_testcancel(), vprintf(__VA_ARGS__)) +#define vwprintf(...) (pthread_testcancel(), vwprintf(__VA_ARGS__)) +#define wcsftime(...) (pthread_testcancel(), wcsftime(__VA_ARGS__)) +#define wordexp(...) (pthread_testcancel(), wordexp(__VA_ARGS__)) +#define wprintf(...) (pthread_testcancel(), wprintf(__VA_ARGS__)) +#define wscanf(...) (pthread_testcancel(), wscanf(__VA_ARGS__)) + +#endif /* WIN_PTHREADS */ diff --git a/src/dabdsp/MathHelper.h b/src/dabdsp/MathHelper.h index 7540038..894f235 100644 --- a/src/dabdsp/MathHelper.h +++ b/src/dabdsp/MathHelper.h @@ -1,224 +1,224 @@ -/* - * Copyright (C) 2018 - * Matthias P. Braendli (matthias.braendli@mpb.li) - * - * Copyright (C) 2017 - * Albrecht Lohofener (albrechtloh@gmx.de) - * - * This file is based on SDR-J - * Copyright (C) 2010, 2011, 2012 - * Jan van Katwijk (J.vanKatwijk@gmail.com) - * - * This file is part of the welle.io. - * Many of the ideas as implemented in welle.io are derived from - * other work, made available through the GNU general Public License. - * All copyrights of the original authors are recognized. - * - * welle.io is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * welle.io 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with welle.io; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - -#ifndef MATHHELPER_H -#define MATHHELPER_H - -#include -#include - -// These conflict with project-wide macros (stdafx.h), only KHz was used by ofdm-processor.cpp -// -//#define Hz(x) (x) -//#define kHz(x) (x * 1000) -//#define MHz(x) (kHz(x) * 1000) - -static inline float get_db_over_256(float x) -{ - return 20 * log10((x + 1.0f) / 256.0f); -} - -static inline float l1_norm(const std::complex& z) -{ - return std::abs(z.real()) + std::abs(z.imag()); -} - -static inline bool check_CRC_bits(const uint8_t *in, int size) -{ - static const uint8_t crcPolynome[] = { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 }; // MSB .. LSB - uint8_t b[16]; - memset(b, 1, 16); - - for (int i = 0; i < size; i++) { - uint8_t d = in[i]; - if (i >= size - 16) { - d ^= 1; - } - - if ((b[0] ^ d) == 1) { - for (int f = 0; f < 15; f++) - b[f] = crcPolynome[f] ^ b[f + 1]; - b[15] = 1; - } - else { - memmove(&b[0], &b[1], sizeof(uint8_t) * 15); // Shift - b[15] = 0; - } - } - - uint16_t crc = 0; - for (int i = 0; i < 16; i++) - crc |= b[i] << i; - return crc == 0; -} - -static inline bool check_crc_bytes(const uint8_t *msg, int len) -{ - uint16_t accumulator = 0xFFFF; - const uint16_t genpoly = 0x1021; - - for (int i = 0; i < len; i++) { - int16_t data = msg[i] << 8; - for (int j = 8; j > 0; j--) { - if ((data ^ accumulator) & 0x8000) - accumulator = ((accumulator << 1) ^ genpoly) & 0xFFFF; - else - accumulator = (accumulator << 1) & 0xFFFF; - data = (data << 1) & 0xFFFF; - } - } - // - // ok, now check with the crc that is contained - // in the au - uint16_t crc = ~((msg[len] << 8) | msg[len + 1]) & 0xFFFF; - return (crc ^ accumulator) == 0; -} - -static inline uint32_t getBits(const uint8_t* d, int16_t offset, uint8_t size) -{ - if (size > 32) { - throw std::logic_error("getBits called with size>32"); - } - - uint32_t res = 0; - - for (int i = 0; i < size; i++) { - res <<= 1; - res |= d[offset + i]; - } - return res; -} - -static inline uint16_t getBits_1(const uint8_t* d, int16_t offset) -{ - return (d[offset] & 0x01); -} - -static inline uint16_t getBits_2(const uint8_t* d, int16_t offset) -{ - uint16_t res = d[offset]; - res <<= 1; - res |= d[offset + 1]; - return res; -} - -static inline uint16_t getBits_3(const uint8_t* d, int16_t offset) -{ - uint16_t res = d[offset]; - res <<= 1; - res |= d[offset + 1]; - res <<= 1; - res |= d[offset + 2]; - return res; -} - -static inline uint16_t getBits_4(const uint8_t* d, int16_t offset) -{ - uint16_t res = d[offset]; - res <<= 1; - res |= d[offset + 1]; - res <<= 1; - res |= d[offset + 2]; - res <<= 1; - res |= d[offset + 3]; - return res; -} - -static inline uint16_t getBits_5(const uint8_t* d, int16_t offset) -{ - uint16_t res = d[offset]; - res <<= 1; - res |= d[offset + 1]; - res <<= 1; - res |= d[offset + 2]; - res <<= 1; - res |= d[offset + 3]; - res <<= 1; - res |= d[offset + 4]; - return res; -} - -static inline uint16_t getBits_6(const uint8_t* d, int16_t offset) -{ - uint16_t res = d[offset]; - res <<= 1; - res |= d[offset + 1]; - res <<= 1; - res |= d[offset + 2]; - res <<= 1; - res |= d[offset + 3]; - res <<= 1; - res |= d[offset + 4]; - res <<= 1; - res |= d[offset + 5]; - return res; -} - -static inline uint16_t getBits_7(const uint8_t* d, int16_t offset) -{ - uint16_t res = d[offset]; - res <<= 1; - res |= d[offset + 1]; - res <<= 1; - res |= d[offset + 2]; - res <<= 1; - res |= d[offset + 3]; - res <<= 1; - res |= d[offset + 4]; - res <<= 1; - res |= d[offset + 5]; - res <<= 1; - res |= d[offset + 6]; - return res; -} - -static inline uint16_t getBits_8(const uint8_t* d, int16_t offset) -{ - uint16_t res = d[offset]; - res <<= 1; - res |= d[offset + 1]; - res <<= 1; - res |= d[offset + 2]; - res <<= 1; - res |= d[offset + 3]; - res <<= 1; - res |= d[offset + 4]; - res <<= 1; - res |= d[offset + 5]; - res <<= 1; - res |= d[offset + 6]; - res <<= 1; - res |= d[offset + 7]; - return res; -} - -#endif // MATHHELPER_H +/* + * Copyright (C) 2018 + * Matthias P. Braendli (matthias.braendli@mpb.li) + * + * Copyright (C) 2017 + * Albrecht Lohofener (albrechtloh@gmx.de) + * + * This file is based on SDR-J + * Copyright (C) 2010, 2011, 2012 + * Jan van Katwijk (J.vanKatwijk@gmail.com) + * + * This file is part of the welle.io. + * Many of the ideas as implemented in welle.io are derived from + * other work, made available through the GNU general Public License. + * All copyrights of the original authors are recognized. + * + * welle.io is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * welle.io 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with welle.io; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef MATHHELPER_H +#define MATHHELPER_H + +#include +#include + +// These conflict with project-wide macros (stdafx.h), only KHz was used by ofdm-processor.cpp +// +//#define Hz(x) (x) +//#define kHz(x) (x * 1000) +//#define MHz(x) (kHz(x) * 1000) + +static inline float get_db_over_256(float x) +{ + return 20 * log10((x + 1.0f) / 256.0f); +} + +static inline float l1_norm(const std::complex& z) +{ + return std::abs(z.real()) + std::abs(z.imag()); +} + +static inline bool check_CRC_bits(const uint8_t *in, int size) +{ + static const uint8_t crcPolynome[] = { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 }; // MSB .. LSB + uint8_t b[16]; + memset(b, 1, 16); + + for (int i = 0; i < size; i++) { + uint8_t d = in[i]; + if (i >= size - 16) { + d ^= 1; + } + + if ((b[0] ^ d) == 1) { + for (int f = 0; f < 15; f++) + b[f] = crcPolynome[f] ^ b[f + 1]; + b[15] = 1; + } + else { + memmove(&b[0], &b[1], sizeof(uint8_t) * 15); // Shift + b[15] = 0; + } + } + + uint16_t crc = 0; + for (int i = 0; i < 16; i++) + crc |= b[i] << i; + return crc == 0; +} + +static inline bool check_crc_bytes(const uint8_t *msg, int len) +{ + uint16_t accumulator = 0xFFFF; + const uint16_t genpoly = 0x1021; + + for (int i = 0; i < len; i++) { + int16_t data = msg[i] << 8; + for (int j = 8; j > 0; j--) { + if ((data ^ accumulator) & 0x8000) + accumulator = ((accumulator << 1) ^ genpoly) & 0xFFFF; + else + accumulator = (accumulator << 1) & 0xFFFF; + data = (data << 1) & 0xFFFF; + } + } + // + // ok, now check with the crc that is contained + // in the au + uint16_t crc = ~((msg[len] << 8) | msg[len + 1]) & 0xFFFF; + return (crc ^ accumulator) == 0; +} + +static inline uint32_t getBits(const uint8_t* d, int16_t offset, uint8_t size) +{ + if (size > 32) { + throw std::logic_error("getBits called with size>32"); + } + + uint32_t res = 0; + + for (int i = 0; i < size; i++) { + res <<= 1; + res |= d[offset + i]; + } + return res; +} + +static inline uint16_t getBits_1(const uint8_t* d, int16_t offset) +{ + return (d[offset] & 0x01); +} + +static inline uint16_t getBits_2(const uint8_t* d, int16_t offset) +{ + uint16_t res = d[offset]; + res <<= 1; + res |= d[offset + 1]; + return res; +} + +static inline uint16_t getBits_3(const uint8_t* d, int16_t offset) +{ + uint16_t res = d[offset]; + res <<= 1; + res |= d[offset + 1]; + res <<= 1; + res |= d[offset + 2]; + return res; +} + +static inline uint16_t getBits_4(const uint8_t* d, int16_t offset) +{ + uint16_t res = d[offset]; + res <<= 1; + res |= d[offset + 1]; + res <<= 1; + res |= d[offset + 2]; + res <<= 1; + res |= d[offset + 3]; + return res; +} + +static inline uint16_t getBits_5(const uint8_t* d, int16_t offset) +{ + uint16_t res = d[offset]; + res <<= 1; + res |= d[offset + 1]; + res <<= 1; + res |= d[offset + 2]; + res <<= 1; + res |= d[offset + 3]; + res <<= 1; + res |= d[offset + 4]; + return res; +} + +static inline uint16_t getBits_6(const uint8_t* d, int16_t offset) +{ + uint16_t res = d[offset]; + res <<= 1; + res |= d[offset + 1]; + res <<= 1; + res |= d[offset + 2]; + res <<= 1; + res |= d[offset + 3]; + res <<= 1; + res |= d[offset + 4]; + res <<= 1; + res |= d[offset + 5]; + return res; +} + +static inline uint16_t getBits_7(const uint8_t* d, int16_t offset) +{ + uint16_t res = d[offset]; + res <<= 1; + res |= d[offset + 1]; + res <<= 1; + res |= d[offset + 2]; + res <<= 1; + res |= d[offset + 3]; + res <<= 1; + res |= d[offset + 4]; + res <<= 1; + res |= d[offset + 5]; + res <<= 1; + res |= d[offset + 6]; + return res; +} + +static inline uint16_t getBits_8(const uint8_t* d, int16_t offset) +{ + uint16_t res = d[offset]; + res <<= 1; + res |= d[offset + 1]; + res <<= 1; + res |= d[offset + 2]; + res <<= 1; + res |= d[offset + 3]; + res <<= 1; + res |= d[offset + 4]; + res <<= 1; + res |= d[offset + 5]; + res <<= 1; + res |= d[offset + 6]; + res <<= 1; + res |= d[offset + 7]; + return res; +} + +#endif // MATHHELPER_H diff --git a/src/dabdsp/Xtan2.cpp b/src/dabdsp/Xtan2.cpp index 8af1202..73f87e5 100644 --- a/src/dabdsp/Xtan2.cpp +++ b/src/dabdsp/Xtan2.cpp @@ -1,94 +1,94 @@ -// -// This LUT implementation of atan2 is a C++ translation of -// a Java discussion on the net -// http://www.java-gaming.org/index.php?topic=14647.0 - -#include "Xtan2.h" - -#ifdef _WINDOWS -#define _USE_MATH_DEFINES -#include -#endif - -#define SIZE 8192 -#define EZIS (-SIZE) - -compAtan::compAtan() : - ATAN2_TABLE_PPY(SIZE + 1), - ATAN2_TABLE_PPX(SIZE + 1), - ATAN2_TABLE_PNY(SIZE + 1), - ATAN2_TABLE_PNX(SIZE + 1), - ATAN2_TABLE_NPY(SIZE + 1), - ATAN2_TABLE_NPX(SIZE + 1), - ATAN2_TABLE_NNY(SIZE + 1), - ATAN2_TABLE_NNX(SIZE + 1) -{ - Stretch = M_PI; - // private static final int SIZE = 1024; - // private static final float Stretch = (float)Math.PI; - // Output will swing from -Stretch to Stretch (default: Math.PI) - // Useful to change to 1 if you would normally do "atan2(y, x) / Math.PI" - - for (int i = 0; i <= SIZE; i++) { - float f = (float)i / SIZE; - ATAN2_TABLE_PPY[i] = atan(f) * Stretch / M_PI; - ATAN2_TABLE_PPX[i] = Stretch * 0.5f - ATAN2_TABLE_PPY[i]; - ATAN2_TABLE_PNY[i] = -ATAN2_TABLE_PPY[i]; - ATAN2_TABLE_PNX[i] = ATAN2_TABLE_PPY[i] - Stretch * 0.5f; - ATAN2_TABLE_NPY[i] = Stretch - ATAN2_TABLE_PPY[i]; - ATAN2_TABLE_NPX[i] = ATAN2_TABLE_PPY[i] + Stretch * 0.5f; - ATAN2_TABLE_NNY[i] = ATAN2_TABLE_PPY[i] - Stretch; - ATAN2_TABLE_NNX[i] = -Stretch * 0.5f - ATAN2_TABLE_PPY[i]; - } -} - -/** - * ATAN2 : performance degrades due to the many "0" tests - */ -float compAtan::atan2(float y, float x) -{ - if (x == 0) { - if (y == 0) return 0; - // return std::numeric_limits::infinity (); - else - if (y > 0) - return M_PI / 2; - else // y < 0 - return - M_PI / 2; - } - - if (x > 0) { - if (y >= 0) { - if (x >= y) - return ATAN2_TABLE_PPY[(int)(SIZE * y / x + 0.5)]; - else - return ATAN2_TABLE_PPX[(int)(SIZE * x / y + 0.5)]; - - } - else { - if (x >= -y) - return ATAN2_TABLE_PNY[(int)(EZIS * y / x + 0.5)]; - else - return ATAN2_TABLE_PNX[(int)(EZIS * x / y + 0.5)]; - } - } - else { - if (y >= 0) { - if (-x >= y) - return ATAN2_TABLE_NPY[(int)(EZIS * y / x + 0.5)]; - else - return ATAN2_TABLE_NPX[(int)(EZIS * x / y + 0.5)]; - } - else { - if (x <= y) // (-x >= -y) - return ATAN2_TABLE_NNY[(int)(SIZE * y / x + 0.5)]; - else - return ATAN2_TABLE_NNX[(int)(SIZE * x / y + 0.5)]; - } - } -} - -float compAtan::argX(DSPCOMPLEX v) -{ - return this->atan2(imag(v), real(v)); -} +// +// This LUT implementation of atan2 is a C++ translation of +// a Java discussion on the net +// http://www.java-gaming.org/index.php?topic=14647.0 + +#include "Xtan2.h" + +#ifdef _WINDOWS +#define _USE_MATH_DEFINES +#include +#endif + +#define SIZE 8192 +#define EZIS (-SIZE) + +compAtan::compAtan() : + ATAN2_TABLE_PPY(SIZE + 1), + ATAN2_TABLE_PPX(SIZE + 1), + ATAN2_TABLE_PNY(SIZE + 1), + ATAN2_TABLE_PNX(SIZE + 1), + ATAN2_TABLE_NPY(SIZE + 1), + ATAN2_TABLE_NPX(SIZE + 1), + ATAN2_TABLE_NNY(SIZE + 1), + ATAN2_TABLE_NNX(SIZE + 1) +{ + Stretch = M_PI; + // private static final int SIZE = 1024; + // private static final float Stretch = (float)Math.PI; + // Output will swing from -Stretch to Stretch (default: Math.PI) + // Useful to change to 1 if you would normally do "atan2(y, x) / Math.PI" + + for (int i = 0; i <= SIZE; i++) { + float f = (float)i / SIZE; + ATAN2_TABLE_PPY[i] = atan(f) * Stretch / M_PI; + ATAN2_TABLE_PPX[i] = Stretch * 0.5f - ATAN2_TABLE_PPY[i]; + ATAN2_TABLE_PNY[i] = -ATAN2_TABLE_PPY[i]; + ATAN2_TABLE_PNX[i] = ATAN2_TABLE_PPY[i] - Stretch * 0.5f; + ATAN2_TABLE_NPY[i] = Stretch - ATAN2_TABLE_PPY[i]; + ATAN2_TABLE_NPX[i] = ATAN2_TABLE_PPY[i] + Stretch * 0.5f; + ATAN2_TABLE_NNY[i] = ATAN2_TABLE_PPY[i] - Stretch; + ATAN2_TABLE_NNX[i] = -Stretch * 0.5f - ATAN2_TABLE_PPY[i]; + } +} + +/** + * ATAN2 : performance degrades due to the many "0" tests + */ +float compAtan::atan2(float y, float x) +{ + if (x == 0) { + if (y == 0) return 0; + // return std::numeric_limits::infinity (); + else + if (y > 0) + return M_PI / 2; + else // y < 0 + return - M_PI / 2; + } + + if (x > 0) { + if (y >= 0) { + if (x >= y) + return ATAN2_TABLE_PPY[(int)(SIZE * y / x + 0.5)]; + else + return ATAN2_TABLE_PPX[(int)(SIZE * x / y + 0.5)]; + + } + else { + if (x >= -y) + return ATAN2_TABLE_PNY[(int)(EZIS * y / x + 0.5)]; + else + return ATAN2_TABLE_PNX[(int)(EZIS * x / y + 0.5)]; + } + } + else { + if (y >= 0) { + if (-x >= y) + return ATAN2_TABLE_NPY[(int)(EZIS * y / x + 0.5)]; + else + return ATAN2_TABLE_NPX[(int)(EZIS * x / y + 0.5)]; + } + else { + if (x <= y) // (-x >= -y) + return ATAN2_TABLE_NNY[(int)(SIZE * y / x + 0.5)]; + else + return ATAN2_TABLE_NNX[(int)(SIZE * x / y + 0.5)]; + } + } +} + +float compAtan::argX(DSPCOMPLEX v) +{ + return this->atan2(imag(v), real(v)); +} diff --git a/src/dabdsp/Xtan2.h b/src/dabdsp/Xtan2.h index f692971..48c9a57 100644 --- a/src/dabdsp/Xtan2.h +++ b/src/dabdsp/Xtan2.h @@ -1,35 +1,35 @@ -// -// This LUT implementation of atan2 is a C++ translation of -// a Java discussion on the net -// http://www.java-gaming.org/index.php?topic=14647.0 - -#ifndef __COMP_ATAN -#define __COMP_ATAN - -#include -#include -#include -#include -#include -#include -#include "dab-constants.h" - -class compAtan -{ - public: - compAtan(void); - float atan2(float y, float x); - float argX(DSPCOMPLEX); - private: - std::vector ATAN2_TABLE_PPY; - std::vector ATAN2_TABLE_PPX; - std::vector ATAN2_TABLE_PNY; - std::vector ATAN2_TABLE_PNX; - std::vector ATAN2_TABLE_NPY; - std::vector ATAN2_TABLE_NPX; - std::vector ATAN2_TABLE_NNY; - std::vector ATAN2_TABLE_NNX; - float Stretch; -}; - -#endif +// +// This LUT implementation of atan2 is a C++ translation of +// a Java discussion on the net +// http://www.java-gaming.org/index.php?topic=14647.0 + +#ifndef __COMP_ATAN +#define __COMP_ATAN + +#include +#include +#include +#include +#include +#include +#include "dab-constants.h" + +class compAtan +{ + public: + compAtan(void); + float atan2(float y, float x); + float argX(DSPCOMPLEX); + private: + std::vector ATAN2_TABLE_PPY; + std::vector ATAN2_TABLE_PPX; + std::vector ATAN2_TABLE_PNY; + std::vector ATAN2_TABLE_PNX; + std::vector ATAN2_TABLE_NPY; + std::vector ATAN2_TABLE_NPX; + std::vector ATAN2_TABLE_NNY; + std::vector ATAN2_TABLE_NNX; + float Stretch; +}; + +#endif diff --git a/src/dabdsp/channels.cpp b/src/dabdsp/channels.cpp index 6a0502d..c49d8d6 100644 --- a/src/dabdsp/channels.cpp +++ b/src/dabdsp/channels.cpp @@ -1,235 +1,235 @@ -/* - * Copyright (C) 2018 - * Matthias P. Braendli (matthias.braendli@mpb.li) - * - * Copyright (C) 2017 - * Albrecht Lohofener (albrechtloh@gmx.de) - * - * This file is based on SDR-J - * Copyright (C) 2010, 2011, 2012 - * Jan van Katwijk (J.vanKatwijk@gmail.com) - * - * This file is part of the welle.io. - * Many of the ideas as implemented in welle.io are derived from - * other work, made available through the GNU general Public License. - * All copyrights of the original authors are recognized. - * - * welle.io is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * welle.io 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with welle.io; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - -#include -#include "channels.h" - -using namespace std; - -string Channels::firstChannel = "5A"; - -Channels::Channels() -{ - // Band III - frequencyMap["5A"] = 174928000; - frequencyMap["5B"] = 176640000; - frequencyMap["5C"] = 178352000; - frequencyMap["5D"] = 180064000; - frequencyMap["6A"] = 181936000; - frequencyMap["6B"] = 183648000; - frequencyMap["6C"] = 185360000; - frequencyMap["6D"] = 187072000; - frequencyMap["7A"] = 188928000; - frequencyMap["7B"] = 190640000; - frequencyMap["7C"] = 192352000; - frequencyMap["7D"] = 194064000; - frequencyMap["8A"] = 195936000; - frequencyMap["8B"] = 197648000; - frequencyMap["8C"] = 199360000; - frequencyMap["8D"] = 201072000; - frequencyMap["9A"] = 202928000; - frequencyMap["9B"] = 204640000; - frequencyMap["9C"] = 206352000; - frequencyMap["9D"] = 208064000; - frequencyMap["10A"] = 209936000; - frequencyMap["10B"] = 211648000; - frequencyMap["10C"] = 213360000; - frequencyMap["10D"] = 215072000; - frequencyMap["11A"] = 216928000; - frequencyMap["11B"] = 218640000; - frequencyMap["11C"] = 220352000; - frequencyMap["11D"] = 222064000; - frequencyMap["12A"] = 223936000; - frequencyMap["12B"] = 225648000; - frequencyMap["12C"] = 227360000; - frequencyMap["12D"] = 229072000; - frequencyMap["13A"] = 230784000; - frequencyMap["13B"] = 232496000; - frequencyMap["13C"] = 234208000; - frequencyMap["13D"] = 235776000; - frequencyMap["13E"] = 237488000; - frequencyMap["13F"] = 239200000; - - // Band L - frequencyMap["LA"] = 1452960000; - frequencyMap["LB"] = 1454672000; - frequencyMap["LC"] = 1456384000; - frequencyMap["LD"] = 1458096000; - frequencyMap["LE"] = 1459808000; - frequencyMap["LF"] = 1461520000; - frequencyMap["LG"] = 1463232000; - frequencyMap["LH"] = 1464944000; - frequencyMap["LI"] = 1466656000; - frequencyMap["LJ"] = 1468368000; - frequencyMap["LK"] = 1470080000; - frequencyMap["LL"] = 1471792000; - frequencyMap["LM"] = 1473504000; - frequencyMap["LN"] = 1475216000; - frequencyMap["LO"] = 1476928000; - frequencyMap["LP"] = 1478640000; - - // Init with first frequency - currentChannel = firstChannel; - currentFrequency = getFrequency(firstChannel); - currentFrequencyIndex = 0; -} - -int Channels::getFrequency(const string& channelName) -{ - int frequency = 0; - - try { - frequency = frequencyMap.at(channelName); - } - catch (const std::out_of_range&) { - clog << "DABConstants: Frequency doesn't exist" << endl; - frequency = 0; - } - - currentFrequency = frequency; - currentChannel = channelName; - - // Get index of current frequency - for (int i=0; i= NUMBEROFCHANNELS) - return ""; - else - return getChannelNameAtIndex(currentFrequencyIndex); -} - -string Channels::getCurrentChannel(void) -{ - return currentChannel; -} - -int Channels::getCurrentFrequency(void) -{ - return currentFrequency; -} - -int Channels::getCurrentIndex() -{ - return currentFrequencyIndex; -} - -string Channels::getChannelNameAtIndex(int index) -{ - string channelName = ""; - - switch(index) - { - // Band III - case 0: channelName = "5A"; break; - case 1: channelName = "5B"; break; - case 2: channelName = "5C"; break; - case 3: channelName = "5D"; break; - case 4: channelName = "6A"; break; - case 5: channelName = "6B"; break; - case 6: channelName = "6C"; break; - case 7: channelName = "6D"; break; - case 8: channelName = "7A"; break; - case 9: channelName = "7B"; break; - case 10: channelName = "7C"; break; - case 11: channelName = "7D"; break; - case 12: channelName = "8A"; break; - case 13: channelName = "8B"; break; - case 14: channelName = "8C"; break; - case 15: channelName = "8D"; break; - case 16: channelName = "9A"; break; - case 17: channelName = "9B"; break; - case 18: channelName = "9C"; break; - case 19: channelName = "9D"; break; - case 20: channelName = "10A"; break; - case 21: channelName = "10B"; break; - case 22: channelName = "10C"; break; - case 23: channelName = "10D"; break; - case 24: channelName = "11A"; break; - case 25: channelName = "11B"; break; - case 26: channelName = "11C"; break; - case 27: channelName = "11D"; break; - case 28: channelName = "12A"; break; - case 29: channelName = "12B"; break; - case 30: channelName = "12C"; break; - case 31: channelName = "12D"; break; - case 32: channelName = "13A"; break; - case 33: channelName = "13B"; break; - case 34: channelName = "13C"; break; - case 35: channelName = "13D"; break; - case 36: channelName = "13E"; break; - case 37: channelName = "13F"; break; - - // Band L - case 38: channelName = "LA"; break; - case 39: channelName = "LB"; break; - case 40: channelName = "LC"; break; - case 41: channelName = "LD"; break; - case 42: channelName = "LE"; break; - case 43: channelName = "LF"; break; - case 44: channelName = "LG"; break; - case 45: channelName = "LH"; break; - case 46: channelName = "LI"; break; - case 47: channelName = "LJ"; break; - case 48: channelName = "LK"; break; - case 49: channelName = "LL"; break; - case 50: channelName = "LM"; break; - case 51: channelName = "LN"; break; - case 52: channelName = "LO"; break; - case 53: channelName = "LP"; break; - default: clog << "DABConstants:" - << "No channel name at index" << - to_string(index) << endl; - } - - return channelName; -} - -std::string Channels::getChannelForFrequency(int frequency) -{ - for (const auto c_f : frequencyMap) { - if (c_f.second == frequency) { - return c_f.first; - } - } - throw out_of_range("frequency is outside channel list"); -} +/* + * Copyright (C) 2018 + * Matthias P. Braendli (matthias.braendli@mpb.li) + * + * Copyright (C) 2017 + * Albrecht Lohofener (albrechtloh@gmx.de) + * + * This file is based on SDR-J + * Copyright (C) 2010, 2011, 2012 + * Jan van Katwijk (J.vanKatwijk@gmail.com) + * + * This file is part of the welle.io. + * Many of the ideas as implemented in welle.io are derived from + * other work, made available through the GNU general Public License. + * All copyrights of the original authors are recognized. + * + * welle.io is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * welle.io 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with welle.io; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include "channels.h" + +using namespace std; + +string Channels::firstChannel = "5A"; + +Channels::Channels() +{ + // Band III + frequencyMap["5A"] = 174928000; + frequencyMap["5B"] = 176640000; + frequencyMap["5C"] = 178352000; + frequencyMap["5D"] = 180064000; + frequencyMap["6A"] = 181936000; + frequencyMap["6B"] = 183648000; + frequencyMap["6C"] = 185360000; + frequencyMap["6D"] = 187072000; + frequencyMap["7A"] = 188928000; + frequencyMap["7B"] = 190640000; + frequencyMap["7C"] = 192352000; + frequencyMap["7D"] = 194064000; + frequencyMap["8A"] = 195936000; + frequencyMap["8B"] = 197648000; + frequencyMap["8C"] = 199360000; + frequencyMap["8D"] = 201072000; + frequencyMap["9A"] = 202928000; + frequencyMap["9B"] = 204640000; + frequencyMap["9C"] = 206352000; + frequencyMap["9D"] = 208064000; + frequencyMap["10A"] = 209936000; + frequencyMap["10B"] = 211648000; + frequencyMap["10C"] = 213360000; + frequencyMap["10D"] = 215072000; + frequencyMap["11A"] = 216928000; + frequencyMap["11B"] = 218640000; + frequencyMap["11C"] = 220352000; + frequencyMap["11D"] = 222064000; + frequencyMap["12A"] = 223936000; + frequencyMap["12B"] = 225648000; + frequencyMap["12C"] = 227360000; + frequencyMap["12D"] = 229072000; + frequencyMap["13A"] = 230784000; + frequencyMap["13B"] = 232496000; + frequencyMap["13C"] = 234208000; + frequencyMap["13D"] = 235776000; + frequencyMap["13E"] = 237488000; + frequencyMap["13F"] = 239200000; + + // Band L + frequencyMap["LA"] = 1452960000; + frequencyMap["LB"] = 1454672000; + frequencyMap["LC"] = 1456384000; + frequencyMap["LD"] = 1458096000; + frequencyMap["LE"] = 1459808000; + frequencyMap["LF"] = 1461520000; + frequencyMap["LG"] = 1463232000; + frequencyMap["LH"] = 1464944000; + frequencyMap["LI"] = 1466656000; + frequencyMap["LJ"] = 1468368000; + frequencyMap["LK"] = 1470080000; + frequencyMap["LL"] = 1471792000; + frequencyMap["LM"] = 1473504000; + frequencyMap["LN"] = 1475216000; + frequencyMap["LO"] = 1476928000; + frequencyMap["LP"] = 1478640000; + + // Init with first frequency + currentChannel = firstChannel; + currentFrequency = getFrequency(firstChannel); + currentFrequencyIndex = 0; +} + +int Channels::getFrequency(const string& channelName) +{ + int frequency = 0; + + try { + frequency = frequencyMap.at(channelName); + } + catch (const std::out_of_range&) { + clog << "DABConstants: Frequency doesn't exist" << endl; + frequency = 0; + } + + currentFrequency = frequency; + currentChannel = channelName; + + // Get index of current frequency + for (int i=0; i= NUMBEROFCHANNELS) + return ""; + else + return getChannelNameAtIndex(currentFrequencyIndex); +} + +string Channels::getCurrentChannel(void) +{ + return currentChannel; +} + +int Channels::getCurrentFrequency(void) +{ + return currentFrequency; +} + +int Channels::getCurrentIndex() +{ + return currentFrequencyIndex; +} + +string Channels::getChannelNameAtIndex(int index) +{ + string channelName = ""; + + switch(index) + { + // Band III + case 0: channelName = "5A"; break; + case 1: channelName = "5B"; break; + case 2: channelName = "5C"; break; + case 3: channelName = "5D"; break; + case 4: channelName = "6A"; break; + case 5: channelName = "6B"; break; + case 6: channelName = "6C"; break; + case 7: channelName = "6D"; break; + case 8: channelName = "7A"; break; + case 9: channelName = "7B"; break; + case 10: channelName = "7C"; break; + case 11: channelName = "7D"; break; + case 12: channelName = "8A"; break; + case 13: channelName = "8B"; break; + case 14: channelName = "8C"; break; + case 15: channelName = "8D"; break; + case 16: channelName = "9A"; break; + case 17: channelName = "9B"; break; + case 18: channelName = "9C"; break; + case 19: channelName = "9D"; break; + case 20: channelName = "10A"; break; + case 21: channelName = "10B"; break; + case 22: channelName = "10C"; break; + case 23: channelName = "10D"; break; + case 24: channelName = "11A"; break; + case 25: channelName = "11B"; break; + case 26: channelName = "11C"; break; + case 27: channelName = "11D"; break; + case 28: channelName = "12A"; break; + case 29: channelName = "12B"; break; + case 30: channelName = "12C"; break; + case 31: channelName = "12D"; break; + case 32: channelName = "13A"; break; + case 33: channelName = "13B"; break; + case 34: channelName = "13C"; break; + case 35: channelName = "13D"; break; + case 36: channelName = "13E"; break; + case 37: channelName = "13F"; break; + + // Band L + case 38: channelName = "LA"; break; + case 39: channelName = "LB"; break; + case 40: channelName = "LC"; break; + case 41: channelName = "LD"; break; + case 42: channelName = "LE"; break; + case 43: channelName = "LF"; break; + case 44: channelName = "LG"; break; + case 45: channelName = "LH"; break; + case 46: channelName = "LI"; break; + case 47: channelName = "LJ"; break; + case 48: channelName = "LK"; break; + case 49: channelName = "LL"; break; + case 50: channelName = "LM"; break; + case 51: channelName = "LN"; break; + case 52: channelName = "LO"; break; + case 53: channelName = "LP"; break; + default: clog << "DABConstants:" + << "No channel name at index" << + to_string(index) << endl; + } + + return channelName; +} + +std::string Channels::getChannelForFrequency(int frequency) +{ + for (const auto c_f : frequencyMap) { + if (c_f.second == frequency) { + return c_f.first; + } + } + throw out_of_range("frequency is outside channel list"); +} diff --git a/src/dabdsp/channels.h b/src/dabdsp/channels.h index 2c579ed..dace0d7 100644 --- a/src/dabdsp/channels.h +++ b/src/dabdsp/channels.h @@ -1,63 +1,63 @@ -/* - * Copyright (C) 2018 - * Matthias P. Braendli (matthias.braendli@mpb.li) - * - * Copyright (C) 2017 - * Albrecht Lohofener (albrechtloh@gmx.de) - * - * This file is based on SDR-J - * Copyright (C) 2010, 2011, 2012 - * Jan van Katwijk (J.vanKatwijk@gmail.com) - * - * This file is part of the welle.io. - * Many of the ideas as implemented in welle.io are derived from - * other work, made available through the GNU general Public License. - * All copyrights of the original authors are recognized. - * - * welle.io is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * welle.io 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with welle.io; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - -#ifndef CHANNELS_H -#define CHANNELS_H - -#include -#include - -#define NUMBEROFCHANNELS 54 - -class Channels -{ -public: - Channels(); - int getFrequency(const std::string& channelName); - std::string getNextChannel(void); - std::string getCurrentChannel(void); - int getCurrentFrequency(void); - int getCurrentIndex(void); - std::string getChannelForFrequency(int frequency); - - static std::string firstChannel; - -private: - std::string getChannelNameAtIndex(int index); - - std::map frequencyMap; - int currentFrequencyIndex; - std::string currentChannel; - int currentFrequency; -}; - -#endif // CCHANNELS_H +/* + * Copyright (C) 2018 + * Matthias P. Braendli (matthias.braendli@mpb.li) + * + * Copyright (C) 2017 + * Albrecht Lohofener (albrechtloh@gmx.de) + * + * This file is based on SDR-J + * Copyright (C) 2010, 2011, 2012 + * Jan van Katwijk (J.vanKatwijk@gmail.com) + * + * This file is part of the welle.io. + * Many of the ideas as implemented in welle.io are derived from + * other work, made available through the GNU general Public License. + * All copyrights of the original authors are recognized. + * + * welle.io is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * welle.io 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with welle.io; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CHANNELS_H +#define CHANNELS_H + +#include +#include + +#define NUMBEROFCHANNELS 54 + +class Channels +{ +public: + Channels(); + int getFrequency(const std::string& channelName); + std::string getNextChannel(void); + std::string getCurrentChannel(void); + int getCurrentFrequency(void); + int getCurrentIndex(void); + std::string getChannelForFrequency(int frequency); + + static std::string firstChannel; + +private: + std::string getChannelNameAtIndex(int index); + + std::map frequencyMap; + int currentFrequencyIndex; + std::string currentChannel; + int currentFrequency; +}; + +#endif // CCHANNELS_H diff --git a/src/dabdsp/char.h b/src/dabdsp/char.h index ef18d60..25efd65 100644 --- a/src/dabdsp/char.h +++ b/src/dabdsp/char.h @@ -1,24 +1,24 @@ -/* Stuff specific to the 8-bit symbol version of the general purpose RS codecs - * - * Copyright 2003, Phil Karn, KA9Q - * May be used under the terms of the GNU Lesser General Public License (LGPL) - */ -typedef unsigned char data_t; - -#define MODNN(x) modnn(rs,x) - -#define MM (rs->mm) -#define NN (rs->nn) -#define ALPHA_TO (rs->alpha_to) -#define INDEX_OF (rs->index_of) -#define GENPOLY (rs->genpoly) -#define NROOTS (rs->nroots) -#define FCR (rs->fcr) -#define PRIM (rs->prim) -#define IPRIM (rs->iprim) -#define PAD (rs->pad) -#define A0 (NN) - - - - +/* Stuff specific to the 8-bit symbol version of the general purpose RS codecs + * + * Copyright 2003, Phil Karn, KA9Q + * May be used under the terms of the GNU Lesser General Public License (LGPL) + */ +typedef unsigned char data_t; + +#define MODNN(x) modnn(rs,x) + +#define MM (rs->mm) +#define NN (rs->nn) +#define ALPHA_TO (rs->alpha_to) +#define INDEX_OF (rs->index_of) +#define GENPOLY (rs->genpoly) +#define NROOTS (rs->nroots) +#define FCR (rs->fcr) +#define PRIM (rs->prim) +#define IPRIM (rs->iprim) +#define PAD (rs->pad) +#define A0 (NN) + + + + diff --git a/src/dabdsp/charsets.cpp b/src/dabdsp/charsets.cpp index 0997a8c..2f3ab5e 100644 --- a/src/dabdsp/charsets.cpp +++ b/src/dabdsp/charsets.cpp @@ -1,207 +1,207 @@ -/* - * Copyright (C) 2015 - * Jan van Katwijk (J.vanKatwijk@gmail.com) - * Lazy Chair Programming - * - * This file is part of the SDR-J (JSDR). - * SDR-J is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * SDR-J 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with SDR-J; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - * This charset handling was kindly added by Przemyslaw Wegrzyn - * all rights acknowledged - */ -#include "charsets.h" -#include -#include -#include -#include - -/** - * This table maps "EBU Latin" charset to corresponding - * Unicode (UCS2-encoded) characters. - * See ETSI TS 101 756 v1.8.1, Annex C - */ -static const unsigned short ebuLatinToUcs2[] = { -/* 0x00 - 0x07 */ 0x00, 0x118, 0x12e, 0x172, 0x102, 0x116, 0x10e, 0x218, -/* 0x08 - 0x0f */ 0x21a, 0x10a, 0xa, 0xb, 0x120, 0x139, 0x17b, 0x143, -/* 0x10 - 0x17 */ 0x105, 0x119, 0x12f, 0x173, 0x103, 0x117, 0x10f, 0x219, -/* 0x18 - 0x1f */ 0x21b, 0x10b, 0x147, 0x11a, 0x121, 0x13a, 0x17c, 0x82, -/* 0x20 - 0x27 */ 0x20, 0x21, 0x22, 0x23, 0x142, 0x25, 0x26, 0x27, -/* 0x28 - 0x2f */ 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, -/* 0x30 - 0x37 */ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, -/* 0x38 - 0x3f */ 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, -/* 0x40 - 0x47 */ 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, -/* 0x48 - 0x4f */ 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, -/* 0x50 - 0x57 */ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, -/* 0x58 - 0x5f */ 0x58, 0x59, 0x5a, 0x5b, 0x16e, 0x5d, 0x141, 0x5f, -/* 0x60 - 0x67 */ 0x104, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, -/* 0x68 - 0x6f */ 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, -/* 0x70 - 0x77 */ 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, -/* 0x78 - 0x7f */ 0x78, 0x79, 0x7a, 0xab, 0x16f, 0xbb, 0x13d, 0x126, -/* 0x80 - 0x87 */ 0xe1, 0xe0, 0xe9, 0xe8, 0xed, 0xec, 0xf3, 0xf2, -/* 0x88 - 0x8f */ 0xfa, 0xf9, 0xd1, 0xc7, 0x15e, 0xdf, 0xa1, 0x178, -/* 0x90 - 0x97 */ 0xe2, 0xe4, 0xea, 0xeb, 0xee, 0xef, 0xf4, 0xf6, -/* 0x98 - 0x9f */ 0xfb, 0xfc, 0xf1, 0xe7, 0x15f, 0x11f, 0x131, 0xff, -/* 0xa0 - 0xa7 */ 0x136, 0x145, 0xa9, 0x122, 0x11e, 0x11b, 0x148, 0x151, -/* 0xa8 - 0xaf */ 0x150, 0x20ac, 0xa3, 0x24, 0x100, 0x112, 0x12a, 0x16a, -/* 0xb0 - 0xb7 */ 0x137, 0x146, 0x13b, 0x123, 0x13c, 0x130, 0x144, 0x171, -/* 0xb8 - 0xbf */ 0x170, 0xbf, 0x13e, 0xb0, 0x101, 0x113, 0x12b, 0x16b, -/* 0xc0 - 0xc7 */ 0xc1, 0xc0, 0xc9, 0xc8, 0xcd, 0xcc, 0xd3, 0xd2, -/* 0xc8 - 0xcf */ 0xda, 0xd9, 0x158, 0x10c, 0x160, 0x17d, 0xd0, 0x13f, -/* 0xd0 - 0xd7 */ 0xc2, 0xc4, 0xca, 0xcb, 0xce, 0xcf, 0xd4, 0xd6, -/* 0xd8 - 0xdf */ 0xdb, 0xdc, 0x159, 0x10d, 0x161, 0x17e, 0x111, 0x140, -/* 0xe0 - 0xe7 */ 0xc3, 0xc5, 0xc6, 0x152, 0x177, 0xdd, 0xd5, 0xd8, -/* 0xe8 - 0xef */ 0xde, 0x14a, 0x154, 0x106, 0x15a, 0x179, 0x164, 0xf0, -/* 0xf0 - 0xf7 */ 0xe3, 0xe5, 0xe6, 0x153, 0x175, 0xfd, 0xf5, 0xf8, -/* 0xf8 - 0xff */ 0xfe, 0x14b, 0x155, 0x107, 0x15b, 0x17a, 0x165, 0x127 -}; - - -//The above table can be automatically generated from the table in ODR-PadEnc (ODR-PadEnc/src/charset.cpp) using the following source code: -// Compile like this: g++ -fPIC -DODRTABLE -I /usr/include/x86_64-linux-gnu/qt5/ -I /usr/include/x86_64-linux-gnu/qt5/QtCore/ -lQt5Core -o chartable charsets.cpp -#ifdef ODRTABLE - -#include -#include -#include - -#include "../../../ODR-PadEnc/src/charset.cpp" - -int main(){ - std::wstring_convert, char16_t> ucs2conv; - std::cout << "/* 0x00 - 0x07 */ 0x00, "; - std::cout << std::setbase(16) << std::setfill(' '); - for (int i=0;i(buffer); - while(*ptr) { num_bytes += 2; ptr++; } - } - - std::unique_ptr utf8(new char[(num_bytes / 2) * 3 + 1]); - uint8_t const* buf = reinterpret_cast(buffer); - - if(num_bytes >= 2) - { - if((buf[0] == 0xfe) && (buf[1] == 0xff)) - { - big_endian = true; - index += 2; - } - else if((buf[0] == 0xff) && (buf[1] == 0xfe)) - { - big_endian = false; - index += 2; - } - } - - for(; index < num_bytes; index += 2) { - - char16_t ch; - - if(big_endian) - ch = (buf[index] << 8) | buf[index + 1]; - else - ch = buf[index] | (buf[index + 1] << 8); - - if(ch < 0x80) - { - utf8[utf8index++] = ch; - } - else if(ch < 0x800) - { - utf8[utf8index++] = 0xc0 | (ch >> 6); - utf8[utf8index++] = 0x80 | (ch & 0x3f); - } - else - { - utf8[utf8index++] = 0xe0 | (ch >> 12); - utf8[utf8index++] = 0x80 | ((ch >> 6) & 0x3f); - utf8[utf8index++] = 0x80 | (ch & 0x3f); - } - } - - utf8[utf8index] = 0; - - return std::string(utf8.get()); -} - -static std::string ebulatin_to_utf8(void const* buffer, size_t num_bytes) -{ - assert(buffer); - - std::u16string ucs2; - uint8_t const* ptr = reinterpret_cast(buffer); - - // NULL-terminated, determine the byte count - if(num_bytes == 0) { - - while(*ptr) { num_bytes++; ptr++; } - } - - // Use the lookup table to convert EBU Latin into UCS-2 - for(size_t index = 0; index < num_bytes; index++) - ucs2 += ebuLatinToUcs2[reinterpret_cast(buffer)[index]]; - - // Convert the UCS-2 into UTF-8 - return ucs2_to_utf8(ucs2.c_str(), 0); -} - -static std::string utf8_to_utf8(void const* buffer, size_t num_bytes) -{ - assert(buffer); - return (num_bytes == 0) ? std::string(reinterpret_cast(buffer)) : - std::string(reinterpret_cast(buffer), num_bytes); -} - -std::string toUtf8StringUsingCharset(const void* buffer, CharacterSet charset, size_t num_bytes) -{ - if(buffer == nullptr) throw std::logic_error("Cannot convert charset of empty buffer"); - - if(charset == CharacterSet::UnicodeUcs2) return ucs2_to_utf8(buffer, num_bytes); - else if(charset == CharacterSet::UnicodeUtf8) return utf8_to_utf8(buffer, num_bytes); - else return ebulatin_to_utf8(buffer, num_bytes); -} - +/* + * Copyright (C) 2015 + * Jan van Katwijk (J.vanKatwijk@gmail.com) + * Lazy Chair Programming + * + * This file is part of the SDR-J (JSDR). + * SDR-J is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * SDR-J 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with SDR-J; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * This charset handling was kindly added by Przemyslaw Wegrzyn + * all rights acknowledged + */ +#include "charsets.h" +#include +#include +#include +#include + +/** + * This table maps "EBU Latin" charset to corresponding + * Unicode (UCS2-encoded) characters. + * See ETSI TS 101 756 v1.8.1, Annex C + */ +static const unsigned short ebuLatinToUcs2[] = { +/* 0x00 - 0x07 */ 0x00, 0x118, 0x12e, 0x172, 0x102, 0x116, 0x10e, 0x218, +/* 0x08 - 0x0f */ 0x21a, 0x10a, 0xa, 0xb, 0x120, 0x139, 0x17b, 0x143, +/* 0x10 - 0x17 */ 0x105, 0x119, 0x12f, 0x173, 0x103, 0x117, 0x10f, 0x219, +/* 0x18 - 0x1f */ 0x21b, 0x10b, 0x147, 0x11a, 0x121, 0x13a, 0x17c, 0x82, +/* 0x20 - 0x27 */ 0x20, 0x21, 0x22, 0x23, 0x142, 0x25, 0x26, 0x27, +/* 0x28 - 0x2f */ 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, +/* 0x30 - 0x37 */ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, +/* 0x38 - 0x3f */ 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, +/* 0x40 - 0x47 */ 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, +/* 0x48 - 0x4f */ 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, +/* 0x50 - 0x57 */ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, +/* 0x58 - 0x5f */ 0x58, 0x59, 0x5a, 0x5b, 0x16e, 0x5d, 0x141, 0x5f, +/* 0x60 - 0x67 */ 0x104, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, +/* 0x68 - 0x6f */ 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, +/* 0x70 - 0x77 */ 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, +/* 0x78 - 0x7f */ 0x78, 0x79, 0x7a, 0xab, 0x16f, 0xbb, 0x13d, 0x126, +/* 0x80 - 0x87 */ 0xe1, 0xe0, 0xe9, 0xe8, 0xed, 0xec, 0xf3, 0xf2, +/* 0x88 - 0x8f */ 0xfa, 0xf9, 0xd1, 0xc7, 0x15e, 0xdf, 0xa1, 0x178, +/* 0x90 - 0x97 */ 0xe2, 0xe4, 0xea, 0xeb, 0xee, 0xef, 0xf4, 0xf6, +/* 0x98 - 0x9f */ 0xfb, 0xfc, 0xf1, 0xe7, 0x15f, 0x11f, 0x131, 0xff, +/* 0xa0 - 0xa7 */ 0x136, 0x145, 0xa9, 0x122, 0x11e, 0x11b, 0x148, 0x151, +/* 0xa8 - 0xaf */ 0x150, 0x20ac, 0xa3, 0x24, 0x100, 0x112, 0x12a, 0x16a, +/* 0xb0 - 0xb7 */ 0x137, 0x146, 0x13b, 0x123, 0x13c, 0x130, 0x144, 0x171, +/* 0xb8 - 0xbf */ 0x170, 0xbf, 0x13e, 0xb0, 0x101, 0x113, 0x12b, 0x16b, +/* 0xc0 - 0xc7 */ 0xc1, 0xc0, 0xc9, 0xc8, 0xcd, 0xcc, 0xd3, 0xd2, +/* 0xc8 - 0xcf */ 0xda, 0xd9, 0x158, 0x10c, 0x160, 0x17d, 0xd0, 0x13f, +/* 0xd0 - 0xd7 */ 0xc2, 0xc4, 0xca, 0xcb, 0xce, 0xcf, 0xd4, 0xd6, +/* 0xd8 - 0xdf */ 0xdb, 0xdc, 0x159, 0x10d, 0x161, 0x17e, 0x111, 0x140, +/* 0xe0 - 0xe7 */ 0xc3, 0xc5, 0xc6, 0x152, 0x177, 0xdd, 0xd5, 0xd8, +/* 0xe8 - 0xef */ 0xde, 0x14a, 0x154, 0x106, 0x15a, 0x179, 0x164, 0xf0, +/* 0xf0 - 0xf7 */ 0xe3, 0xe5, 0xe6, 0x153, 0x175, 0xfd, 0xf5, 0xf8, +/* 0xf8 - 0xff */ 0xfe, 0x14b, 0x155, 0x107, 0x15b, 0x17a, 0x165, 0x127 +}; + + +//The above table can be automatically generated from the table in ODR-PadEnc (ODR-PadEnc/src/charset.cpp) using the following source code: +// Compile like this: g++ -fPIC -DODRTABLE -I /usr/include/x86_64-linux-gnu/qt5/ -I /usr/include/x86_64-linux-gnu/qt5/QtCore/ -lQt5Core -o chartable charsets.cpp +#ifdef ODRTABLE + +#include +#include +#include + +#include "../../../ODR-PadEnc/src/charset.cpp" + +int main(){ + std::wstring_convert, char16_t> ucs2conv; + std::cout << "/* 0x00 - 0x07 */ 0x00, "; + std::cout << std::setbase(16) << std::setfill(' '); + for (int i=0;i(buffer); + while(*ptr) { num_bytes += 2; ptr++; } + } + + std::unique_ptr utf8(new char[(num_bytes / 2) * 3 + 1]); + uint8_t const* buf = reinterpret_cast(buffer); + + if(num_bytes >= 2) + { + if((buf[0] == 0xfe) && (buf[1] == 0xff)) + { + big_endian = true; + index += 2; + } + else if((buf[0] == 0xff) && (buf[1] == 0xfe)) + { + big_endian = false; + index += 2; + } + } + + for(; index < num_bytes; index += 2) { + + char16_t ch; + + if(big_endian) + ch = (buf[index] << 8) | buf[index + 1]; + else + ch = buf[index] | (buf[index + 1] << 8); + + if(ch < 0x80) + { + utf8[utf8index++] = ch; + } + else if(ch < 0x800) + { + utf8[utf8index++] = 0xc0 | (ch >> 6); + utf8[utf8index++] = 0x80 | (ch & 0x3f); + } + else + { + utf8[utf8index++] = 0xe0 | (ch >> 12); + utf8[utf8index++] = 0x80 | ((ch >> 6) & 0x3f); + utf8[utf8index++] = 0x80 | (ch & 0x3f); + } + } + + utf8[utf8index] = 0; + + return std::string(utf8.get()); +} + +static std::string ebulatin_to_utf8(void const* buffer, size_t num_bytes) +{ + assert(buffer); + + std::u16string ucs2; + uint8_t const* ptr = reinterpret_cast(buffer); + + // NULL-terminated, determine the byte count + if(num_bytes == 0) { + + while(*ptr) { num_bytes++; ptr++; } + } + + // Use the lookup table to convert EBU Latin into UCS-2 + for(size_t index = 0; index < num_bytes; index++) + ucs2 += ebuLatinToUcs2[reinterpret_cast(buffer)[index]]; + + // Convert the UCS-2 into UTF-8 + return ucs2_to_utf8(ucs2.c_str(), 0); +} + +static std::string utf8_to_utf8(void const* buffer, size_t num_bytes) +{ + assert(buffer); + return (num_bytes == 0) ? std::string(reinterpret_cast(buffer)) : + std::string(reinterpret_cast(buffer), num_bytes); +} + +std::string toUtf8StringUsingCharset(const void* buffer, CharacterSet charset, size_t num_bytes) +{ + if(buffer == nullptr) throw std::logic_error("Cannot convert charset of empty buffer"); + + if(charset == CharacterSet::UnicodeUcs2) return ucs2_to_utf8(buffer, num_bytes); + else if(charset == CharacterSet::UnicodeUtf8) return utf8_to_utf8(buffer, num_bytes); + else return ebulatin_to_utf8(buffer, num_bytes); +} + diff --git a/src/dabdsp/charsets.h b/src/dabdsp/charsets.h index 05d266c..8b46a4f 100644 --- a/src/dabdsp/charsets.h +++ b/src/dabdsp/charsets.h @@ -1,52 +1,52 @@ -/* - * Copyright (C) 2013 - * Jan van Katwijk (J.vanKatwijk@gmail.com) - * Lazy Chair Programming - * - * This file is part of the SDR-J (JSDR). - * SDR-J is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * SDR-J 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with SDR-J; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - * This charset handling was kindly added by Przemyslaw Wegrzyn - * all rights acknowledged - */ -#ifndef __CHARSETS_H -#define __CHARSETS_H - -#include -#include - -/* - * Codes assigned to character sets, as defined - * in ETSI TS 101 756 v1.6.1, section 5.2. - */ -enum class CharacterSet : uint8_t { - EbuLatin = 0x00, // Complete EBU Latin based repertoire - see annex C - UnicodeUcs2 = 0x06, - UnicodeUtf8 = 0x0F, - Undefined, -}; - -/** - * Converts the string from the given charset to a UTF-8 - * encoded string. - * - * If num_bytes is nonzero, the buffer must be zero - * terminated. - */ -std::string toUtf8StringUsingCharset(const void* buffer, - CharacterSet charset, size_t num_bytes = 0); - -#endif // CHARSETS_H - +/* + * Copyright (C) 2013 + * Jan van Katwijk (J.vanKatwijk@gmail.com) + * Lazy Chair Programming + * + * This file is part of the SDR-J (JSDR). + * SDR-J is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * SDR-J 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with SDR-J; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * This charset handling was kindly added by Przemyslaw Wegrzyn + * all rights acknowledged + */ +#ifndef __CHARSETS_H +#define __CHARSETS_H + +#include +#include + +/* + * Codes assigned to character sets, as defined + * in ETSI TS 101 756 v1.6.1, section 5.2. + */ +enum class CharacterSet : uint8_t { + EbuLatin = 0x00, // Complete EBU Latin based repertoire - see annex C + UnicodeUcs2 = 0x06, + UnicodeUtf8 = 0x0F, + Undefined, +}; + +/** + * Converts the string from the given charset to a UTF-8 + * encoded string. + * + * If num_bytes is nonzero, the buffer must be zero + * terminated. + */ +std::string toUtf8StringUsingCharset(const void* buffer, + CharacterSet charset, size_t num_bytes = 0); + +#endif // CHARSETS_H + diff --git a/src/dabdsp/dab-audio.cpp b/src/dabdsp/dab-audio.cpp index e152e93..4cfb73a 100644 --- a/src/dabdsp/dab-audio.cpp +++ b/src/dabdsp/dab-audio.cpp @@ -1,165 +1,165 @@ -/* - * Copyright (C) 2018 - * Matthias P. Braendli (matthias.braendli@mpb.li) - * - * Copyright (C) 2013 - * Jan van Katwijk (J.vanKatwijk@gmail.com) - * Lazy Chair Programming - * - * This file is part of the SDR-J (JSDR). - * SDR-J is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * SDR-J 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with SDR-J; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#include -#include -#include "dab-constants.h" -#include "dab-audio.h" -#include "decoder_adapter.h" -#include "eep-protection.h" -#include "uep-protection.h" -#include "profiling.h" - -// As an experiment a version of the backend is created -// that will be running in a separate thread. Might be -// useful for multicore processors. -// -// Interleaving is - for reasons of simplicity - done -// inline rather than through a special class-object -//static -//int8_t interleaveDelays[] = { -// 15, 7, 11, 3, 13, 5, 9, 1, 14, 6, 10, 2, 12, 4, 8, 0}; -// -// -// fragmentsize == Length * CUSize -DabAudio::DabAudio( - AudioServiceComponentType dabModus, - int16_t fragmentSize, - int16_t bitRate, - ProtectionSettings protection, - ProgrammeHandlerInterface& phi, - const std::string& dumpFileName) : - myProgrammeHandler(phi), - mscBuffer(64 * 32768), - dumpFileName(dumpFileName) -{ - this->dabModus = dabModus; - this->fragmentSize = fragmentSize; - this->bitRate = bitRate; - - outV.resize(bitRate * 24); - for (int i = 0; i < 16; i ++) { - interleaveData[i].resize(fragmentSize); - } - - using std::make_unique; - - if (protection.shortForm) { - protectionHandler = make_aligned(bitRate, protection.uepLevel); - } - else { - const bool profile_is_eep_a = - protection.eepProfile == EEPProtectionProfile::EEP_A; - protectionHandler = make_aligned( - bitRate, profile_is_eep_a, (int)protection.eepLevel); - } - - our_dabProcessor = make_unique( - myProgrammeHandler, bitRate, dabModus, dumpFileName); - - running = true; - ourThread = std::thread(&DabAudio::run, this); -} - -DabAudio::~DabAudio() -{ - running = false; - - if (ourThread.joinable()) { - mscDataAvailable.notify_all(); - ourThread.join(); - } -} - -int32_t DabAudio::process(const softbit_t *v, int16_t cnt) -{ - int32_t fr; - - if (mscBuffer.GetRingBufferWriteAvailable () < cnt) - fprintf (stderr, "dab-concurrent: buffer full\n"); - - while ((fr = mscBuffer.GetRingBufferWriteAvailable ()) <= cnt) { - if (!running) - return 0; - std::this_thread::sleep_for(std::chrono::microseconds(1)); - } - - mscBuffer.putDataIntoBuffer(v, cnt); - mscDataAvailable.notify_all(); - return fr; -} - -const int16_t interleaveMap[] = {0,8,4,12,2,10,6,14,1,9,5,13,3,11,7,15}; - -void DabAudio::run() -{ - int16_t i; - int16_t countforInterleaver = 0; - int16_t interleaverIndex = 0; - std::vector data(fragmentSize); - std::vector tempX(fragmentSize); - - while (running) { - std::unique_lock lock(ourMutex); - while (running && mscBuffer.GetRingBufferReadAvailable() <= fragmentSize) { - mscDataAvailable.wait(lock); - } - if (!running) - break; - - // mscBuffer is threadsafe to access, no need to keep the lock - lock.unlock(); - - PROFILE(DAGetMSCData); - mscBuffer.getDataFromBuffer(data.data(), fragmentSize); - - PROFILE(DADeinterleave); - for (i = 0; i < fragmentSize; i ++) { - tempX[i] = interleaveData[(interleaverIndex + - interleaveMap[i & 017]) & 017][i]; - interleaveData[interleaverIndex][i] = data[i]; - } - interleaverIndex = (interleaverIndex + 1) & 0x0F; - - // only continue when de-interleaver is filled - if (countforInterleaver <= 15) { - countforInterleaver ++; - continue; - } - - PROFILE(DADeconvolve); - protectionHandler->deconvolve(tempX.data(), fragmentSize, outV.data()); - - PROFILE(DADispersal); - // and the inline energy dispersal - energyDispersal.dedisperse(outV); - - if (our_dabProcessor) { - PROFILE(DADecode); - our_dabProcessor->addtoFrame(outV.data()); - } - PROFILE(DADone); - } -} - +/* + * Copyright (C) 2018 + * Matthias P. Braendli (matthias.braendli@mpb.li) + * + * Copyright (C) 2013 + * Jan van Katwijk (J.vanKatwijk@gmail.com) + * Lazy Chair Programming + * + * This file is part of the SDR-J (JSDR). + * SDR-J is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * SDR-J 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with SDR-J; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include "dab-constants.h" +#include "dab-audio.h" +#include "decoder_adapter.h" +#include "eep-protection.h" +#include "uep-protection.h" +#include "profiling.h" + +// As an experiment a version of the backend is created +// that will be running in a separate thread. Might be +// useful for multicore processors. +// +// Interleaving is - for reasons of simplicity - done +// inline rather than through a special class-object +//static +//int8_t interleaveDelays[] = { +// 15, 7, 11, 3, 13, 5, 9, 1, 14, 6, 10, 2, 12, 4, 8, 0}; +// +// +// fragmentsize == Length * CUSize +DabAudio::DabAudio( + AudioServiceComponentType dabModus, + int16_t fragmentSize, + int16_t bitRate, + ProtectionSettings protection, + ProgrammeHandlerInterface& phi, + const std::string& dumpFileName) : + myProgrammeHandler(phi), + mscBuffer(64 * 32768), + dumpFileName(dumpFileName) +{ + this->dabModus = dabModus; + this->fragmentSize = fragmentSize; + this->bitRate = bitRate; + + outV.resize(bitRate * 24); + for (int i = 0; i < 16; i ++) { + interleaveData[i].resize(fragmentSize); + } + + using std::make_unique; + + if (protection.shortForm) { + protectionHandler = make_aligned(bitRate, protection.uepLevel); + } + else { + const bool profile_is_eep_a = + protection.eepProfile == EEPProtectionProfile::EEP_A; + protectionHandler = make_aligned( + bitRate, profile_is_eep_a, (int)protection.eepLevel); + } + + our_dabProcessor = make_unique( + myProgrammeHandler, bitRate, dabModus, dumpFileName); + + running = true; + ourThread = std::thread(&DabAudio::run, this); +} + +DabAudio::~DabAudio() +{ + running = false; + + if (ourThread.joinable()) { + mscDataAvailable.notify_all(); + ourThread.join(); + } +} + +int32_t DabAudio::process(const softbit_t *v, int16_t cnt) +{ + int32_t fr; + + if (mscBuffer.GetRingBufferWriteAvailable () < cnt) + fprintf (stderr, "dab-concurrent: buffer full\n"); + + while ((fr = mscBuffer.GetRingBufferWriteAvailable ()) <= cnt) { + if (!running) + return 0; + std::this_thread::sleep_for(std::chrono::microseconds(1)); + } + + mscBuffer.putDataIntoBuffer(v, cnt); + mscDataAvailable.notify_all(); + return fr; +} + +const int16_t interleaveMap[] = {0,8,4,12,2,10,6,14,1,9,5,13,3,11,7,15}; + +void DabAudio::run() +{ + int16_t i; + int16_t countforInterleaver = 0; + int16_t interleaverIndex = 0; + std::vector data(fragmentSize); + std::vector tempX(fragmentSize); + + while (running) { + std::unique_lock lock(ourMutex); + while (running && mscBuffer.GetRingBufferReadAvailable() <= fragmentSize) { + mscDataAvailable.wait(lock); + } + if (!running) + break; + + // mscBuffer is threadsafe to access, no need to keep the lock + lock.unlock(); + + PROFILE(DAGetMSCData); + mscBuffer.getDataFromBuffer(data.data(), fragmentSize); + + PROFILE(DADeinterleave); + for (i = 0; i < fragmentSize; i ++) { + tempX[i] = interleaveData[(interleaverIndex + + interleaveMap[i & 017]) & 017][i]; + interleaveData[interleaverIndex][i] = data[i]; + } + interleaverIndex = (interleaverIndex + 1) & 0x0F; + + // only continue when de-interleaver is filled + if (countforInterleaver <= 15) { + countforInterleaver ++; + continue; + } + + PROFILE(DADeconvolve); + protectionHandler->deconvolve(tempX.data(), fragmentSize, outV.data()); + + PROFILE(DADispersal); + // and the inline energy dispersal + energyDispersal.dedisperse(outV); + + if (our_dabProcessor) { + PROFILE(DADecode); + our_dabProcessor->addtoFrame(outV.data()); + } + PROFILE(DADone); + } +} + diff --git a/src/dabdsp/dab-audio.h b/src/dabdsp/dab-audio.h index 101aa38..b68e67e 100644 --- a/src/dabdsp/dab-audio.h +++ b/src/dabdsp/dab-audio.h @@ -1,83 +1,83 @@ -/* - * Copyright (C) 2018 - * Matthias P. Braendli (matthias.braendli@mpb.li) - * - * Copyright (C) 2013 - * Jan van Katwijk (J.vanKatwijk@gmail.com) - * Lazy Chair Programming - * - * This file is part of the SDR-J (JSDR). - * SDR-J is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * SDR-J 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with SDR-J; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ -#ifndef __DAB_AUDIO -#define __DAB_AUDIO - -#include "dab-virtual.h" -#include -#include -#include -#include -#include -#include -#include -#include "ringbuffer.h" -#include "energy_dispersal.h" -#include "radio-controller.h" - -class DabProcessor; -class Protection; - -class DabAudio : public DabVirtual -{ - public: - DabAudio(AudioServiceComponentType dabModus, - int16_t fragmentSize, - int16_t bitRate, - ProtectionSettings protection, - ProgrammeHandlerInterface& phi, - const std::string& dumpFileName); - virtual ~DabAudio(void); - DabAudio(const DabAudio&) = delete; - DabAudio& operator=(const DabAudio&) = delete; - - int32_t process(const softbit_t *v, int16_t cnt); - - protected: - ProgrammeHandlerInterface& myProgrammeHandler; - - private: - void run(void); - std::atomic running; - AudioServiceComponentType dabModus; - int16_t fragmentSize; - int16_t bitRate; - std::vector outV; - std::vector interleaveData[16]; - EnergyDispersal energyDispersal; - - std::condition_variable mscDataAvailable; - std::mutex ourMutex; - std::thread ourThread; - - aligned_ptr protectionHandler; - std::unique_ptr our_dabProcessor; - RingBuffer mscBuffer; - - const std::string dumpFileName; -}; - -#endif - +/* + * Copyright (C) 2018 + * Matthias P. Braendli (matthias.braendli@mpb.li) + * + * Copyright (C) 2013 + * Jan van Katwijk (J.vanKatwijk@gmail.com) + * Lazy Chair Programming + * + * This file is part of the SDR-J (JSDR). + * SDR-J is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * SDR-J 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with SDR-J; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __DAB_AUDIO +#define __DAB_AUDIO + +#include "dab-virtual.h" +#include +#include +#include +#include +#include +#include +#include +#include "ringbuffer.h" +#include "energy_dispersal.h" +#include "radio-controller.h" + +class DabProcessor; +class Protection; + +class DabAudio : public DabVirtual +{ + public: + DabAudio(AudioServiceComponentType dabModus, + int16_t fragmentSize, + int16_t bitRate, + ProtectionSettings protection, + ProgrammeHandlerInterface& phi, + const std::string& dumpFileName); + virtual ~DabAudio(void); + DabAudio(const DabAudio&) = delete; + DabAudio& operator=(const DabAudio&) = delete; + + int32_t process(const softbit_t *v, int16_t cnt); + + protected: + ProgrammeHandlerInterface& myProgrammeHandler; + + private: + void run(void); + std::atomic running; + AudioServiceComponentType dabModus; + int16_t fragmentSize; + int16_t bitRate; + std::vector outV; + std::vector interleaveData[16]; + EnergyDispersal energyDispersal; + + std::condition_variable mscDataAvailable; + std::mutex ourMutex; + std::thread ourThread; + + aligned_ptr protectionHandler; + std::unique_ptr our_dabProcessor; + RingBuffer mscBuffer; + + const std::string dumpFileName; +}; + +#endif + diff --git a/src/dabdsp/dab-constants.cpp b/src/dabdsp/dab-constants.cpp index 45db61d..6024993 100644 --- a/src/dabdsp/dab-constants.cpp +++ b/src/dabdsp/dab-constants.cpp @@ -1,530 +1,530 @@ -/* - * Copyright (C) 2018 - * Matthias P. Braendli (matthias.braendli@mpb.li) - * - * Copyright (C) 2017 - * Albrecht Lohofener (albrechtloh@gmx.de) - * - * This file is part of the welle.io. - * Many of the ideas as implemented in welle.io are derived from - * other work, made available through the GNU general Public License. - * All copyrights of the original authors are recognized. - * - * welle.io is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * welle.io 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with welle.io; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - -#include "dab-constants.h" -#include -#include -#include -#include - -// For Qt translation if Qt is existing -#ifdef QT_CORE_LIB - #include -#else - #define QT_TR_NOOP(x) (x) -#endif - -using namespace std; - -// Table ETSI EN 300 401 Page 50 -// Table is copied from the work of Michael Hoehn -const int ProtLevel[64][3] = { - {16,5,32}, // Index 0 - {21,4,32}, - {24,3,32}, - {29,2,32}, - {35,1,32}, // Index 4 - {24,5,48}, - {29,4,48}, - {35,3,48}, - {42,2,48}, - {52,1,48}, // Index 9 - {29,5,56}, - {35,4,56}, - {42,3,56}, - {52,2,56}, - {32,5,64}, // Index 14 - {42,4,64}, - {48,3,64}, - {58,2,64}, - {70,1,64}, - {40,5,80}, // Index 19 - {52,4,80}, - {58,3,80}, - {70,2,80}, - {84,1,80}, - {48,5,96}, // Index 24 - {58,4,96}, - {70,3,96}, - {84,2,96}, - {104,1,96}, - {58,5,112}, // Index 29 - {70,4,112}, - {84,3,112}, - {104,2,112}, - {64,5,128}, - {84,4,128}, // Index 34 - {96,3,128}, - {116,2,128}, - {140,1,128}, - {80,5,160}, - {104,4,160}, // Index 39 - {116,3,160}, - {140,2,160}, - {168,1,160}, - {96,5,192}, - {116,4,192}, // Index 44 - {140,3,192}, - {168,2,192}, - {208,1,192}, - {116,5,224}, - {140,4,224}, // Index 49 - {168,3,224}, - {208,2,224}, - {232,1,224}, - {128,5,256}, - {168,4,256}, // Index 54 - {192,3,256}, - {232,2,256}, - {280,1,256}, - {160,5,320}, - {208,4,320}, // index 59 - {280,2,320}, - {192,5,384}, - {280,3,384}, - {416,1,384}}; - - -static std::string flag_to_shortlabel(const std::string& label, uint16_t flag) -{ - stringstream shortlabel; - for (size_t i = 0; i < label.size(); ++i) { - if (flag & 0x8000 >> i) { - shortlabel << label[i]; - } - } - - return shortlabel.str(); -} - -string DabLabel::utf8_label() const -{ - const auto fig2 = fig2_label(); - if (not fig2.empty()) { - return fig2; - } - else { - return fig1_label_utf8(); - } -} - -string DabLabel::fig1_label_utf8() const -{ - return toUtf8StringUsingCharset(fig1_label.c_str(), charset); -} - -string DabLabel::fig1_shortlabel_utf8() const -{ - const string shortlabel = flag_to_shortlabel(fig1_label, fig1_flag); - return toUtf8StringUsingCharset(shortlabel.c_str(), charset); -} - -void DabLabel::setCharset(uint8_t charset_id) -{ - charset = static_cast(charset_id); -} - -string DabLabel::fig2_label() const -{ - vector segments_cat; - for (size_t i = 0; i < segment_count; i++) { - if (segments.count(i) == 0) { - return ""; - } - else { - const auto& s = segments.at(i); - copy(s.begin(), s.end(), back_inserter(segments_cat)); - } - } - - switch (extended_label_charset) { - case CharacterSet::EbuLatin: - //std::clog << "DABConstants: FIG2 label encoded in EBU Latin is not allowed." << std::endl; - return ""; // Fallback to FIG1 - case CharacterSet::UnicodeUtf8: - return string(segments_cat.begin(), segments_cat.end()); - case CharacterSet::UnicodeUcs2: - return toUtf8StringUsingCharset( - segments_cat.data(), CharacterSet::UnicodeUcs2, segments_cat.size()); - case CharacterSet::Undefined: - return ""; - } - throw logic_error("invalid extended label charset " + to_string((int)extended_label_charset)); -} - -const char* DABConstants::getProgramTypeName(int type) -{ - const char* typeName = ""; - switch (type) { - case 0: typeName = ""; break; - case 1: typeName = QT_TR_NOOP("News"); break; - case 2: typeName = QT_TR_NOOP("Current Affairs"); break; - case 3: typeName = QT_TR_NOOP("Information"); break; - case 4: typeName = QT_TR_NOOP("Sport"); break; - case 5: typeName = QT_TR_NOOP("Education"); break; - case 6: typeName = QT_TR_NOOP("Drama"); break; - case 7: typeName = QT_TR_NOOP("Arts"); break; - case 8: typeName = QT_TR_NOOP("Science"); break; - case 9: typeName = QT_TR_NOOP("Talk"); break; - case 10: typeName = QT_TR_NOOP("Pop Music"); break; - case 11: typeName = QT_TR_NOOP("Rock Music"); break; - case 12: typeName = QT_TR_NOOP("Easy Listening"); break; - case 13: typeName = QT_TR_NOOP("Light classical"); break; - case 14: typeName = QT_TR_NOOP("Classical Music"); break; - case 15: typeName = QT_TR_NOOP("Other Music"); break; - case 16: typeName = QT_TR_NOOP("Weather"); break; - case 17: typeName = QT_TR_NOOP("Finance"); break; - case 18: typeName = QT_TR_NOOP("Children\'s"); break; - case 19: typeName = QT_TR_NOOP("Factual"); break; - case 20: typeName = QT_TR_NOOP("Religion"); break; - case 21: typeName = QT_TR_NOOP("Phone In"); break; - case 22: typeName = QT_TR_NOOP("Travel"); break; - case 23: typeName = QT_TR_NOOP("Leisure"); break; - case 24: typeName = QT_TR_NOOP("Jazz and Blues"); break; - case 25: typeName = QT_TR_NOOP("Country Music"); break; - case 26: typeName = QT_TR_NOOP("National Music"); break; - case 27: typeName = QT_TR_NOOP("Oldies Music"); break; - case 28: typeName = QT_TR_NOOP("Folk Music"); break; - case 29: typeName = QT_TR_NOOP("Documentary"); break; - case 30: typeName = QT_TR_NOOP("entry 30 not used"); break; - case 31: typeName = QT_TR_NOOP("entry 31 not used"); break; - default: typeName = QT_TR_NOOP("UNKNOWN"); - //std::clog << "DABConstants: Unknown program type" << std::endl; - break; - } - - return typeName; -} - -const char* DABConstants::getLanguageName(int language) -{ - const char* languageName = ""; - - switch (language) { - case 0: languageName = ""; break; - case 1: languageName = QT_TR_NOOP("Albanian"); break; - case 2: languageName = QT_TR_NOOP("Breton"); break; - case 3: languageName = QT_TR_NOOP("Catalan"); break; - case 4: languageName = QT_TR_NOOP("Croatian"); break; - case 5: languageName = QT_TR_NOOP("Welsh"); break; - case 6: languageName = QT_TR_NOOP("Czech"); break; - case 7: languageName = QT_TR_NOOP("Danish"); break; - case 8: languageName = QT_TR_NOOP("German"); break; - case 9: languageName = QT_TR_NOOP("English"); break; - case 10: languageName = QT_TR_NOOP("Spanish"); break; - case 11: languageName = QT_TR_NOOP("Esperanto"); break; - case 12: languageName = QT_TR_NOOP("Estonian"); break; - case 13: languageName = QT_TR_NOOP("Basque"); break; - case 14: languageName = QT_TR_NOOP("Faroese"); break; - case 15: languageName = QT_TR_NOOP("French"); break; - case 16: languageName = QT_TR_NOOP("Frisian"); break; - case 17: languageName = QT_TR_NOOP("Irish"); break; - case 18: languageName = QT_TR_NOOP("Gaelic"); break; - case 19: languageName = QT_TR_NOOP("Galician"); break; - case 20: languageName = QT_TR_NOOP("Icelandic"); break; - case 21: languageName = QT_TR_NOOP("Italian"); break; - case 22: languageName = QT_TR_NOOP("Lappish"); break; - case 23: languageName = QT_TR_NOOP("Latin"); break; - case 24: languageName = QT_TR_NOOP("Latvian"); break; - case 25: languageName = QT_TR_NOOP("Luxembourgian"); break; - case 26: languageName = QT_TR_NOOP("Lithuanian"); break; - case 27: languageName = QT_TR_NOOP("Hungarian"); break; - case 28: languageName = QT_TR_NOOP("Maltese"); break; - case 29: languageName = QT_TR_NOOP("Dutch"); break; - case 30: languageName = QT_TR_NOOP("Norwegian"); break; - case 31: languageName = QT_TR_NOOP("Occitan"); break; - case 32: languageName = QT_TR_NOOP("Polish"); break; - case 33: languageName = QT_TR_NOOP("Portuguese"); break; - case 34: languageName = QT_TR_NOOP("Romanian"); break; - case 35: languageName = QT_TR_NOOP("Romansh"); break; - case 36: languageName = QT_TR_NOOP("Serbian"); break; - case 37: languageName = QT_TR_NOOP("Slovak"); break; - case 38: languageName = QT_TR_NOOP("Slovene"); break; - case 39: languageName = QT_TR_NOOP("Finnish"); break; - case 40: languageName = QT_TR_NOOP("Swedish"); break; - case 41: languageName = QT_TR_NOOP("Turkish"); break; - case 42: languageName = QT_TR_NOOP("Flemish"); break; - case 43: languageName = QT_TR_NOOP("Walloon"); break; - case 64: languageName = QT_TR_NOOP("Background sound/clean feed"); break; - case 69: languageName = QT_TR_NOOP("Zulu"); break; - case 70: languageName = QT_TR_NOOP("Vietnamese"); break; - case 71: languageName = QT_TR_NOOP("Uzbek"); break; - case 72: languageName = QT_TR_NOOP("Urdu"); break; - case 73: languageName = QT_TR_NOOP("Ukrainian"); break; - case 74: languageName = QT_TR_NOOP("Thai"); break; - case 75: languageName = QT_TR_NOOP("Telugu"); break; - case 76: languageName = QT_TR_NOOP("Tatar"); break; - case 77: languageName = QT_TR_NOOP("Tamil"); break; - case 78: languageName = QT_TR_NOOP("Tadzhik"); break; - case 79: languageName = QT_TR_NOOP("Swahili"); break; - case 80: languageName = QT_TR_NOOP("Sranan Tongo"); break; - case 81: languageName = QT_TR_NOOP("Somali"); break; - case 82: languageName = QT_TR_NOOP("Sinhalese"); break; - case 83: languageName = QT_TR_NOOP("Shona"); break; - case 84: languageName = QT_TR_NOOP("Serbo-Croat"); break; - case 85: languageName = QT_TR_NOOP("Rusyn"); break; - case 86: languageName = QT_TR_NOOP("Russian"); break; - case 87: languageName = QT_TR_NOOP("Quechua"); break; - case 88: languageName = QT_TR_NOOP("Pushtu"); break; - case 89: languageName = QT_TR_NOOP("Punjabi"); break; - case 90: languageName = QT_TR_NOOP("Persian"); break; - case 91: languageName = QT_TR_NOOP("Papiamento"); break; - case 92: languageName = QT_TR_NOOP("Oriya"); break; - case 93: languageName = QT_TR_NOOP("Nepali"); break; - case 94: languageName = QT_TR_NOOP("Ndebele"); break; - case 95: languageName = QT_TR_NOOP("Marathi"); break; - case 96: languageName = QT_TR_NOOP("Moldavian"); break; - case 97: languageName = QT_TR_NOOP("Malaysian"); break; - case 98: languageName = QT_TR_NOOP("Malagasay"); break; - case 99: languageName = QT_TR_NOOP("Macedonian"); break; - case 100: languageName = QT_TR_NOOP("Laotian"); break; - case 101: languageName = QT_TR_NOOP("Korean"); break; - case 102: languageName = QT_TR_NOOP("Khmer"); break; - case 103: languageName = QT_TR_NOOP("Kazakh"); break; - case 104: languageName = QT_TR_NOOP("Kannada"); break; - case 105: languageName = QT_TR_NOOP("Japanese"); break; - case 106: languageName = QT_TR_NOOP("Indonesian"); break; - case 107: languageName = QT_TR_NOOP("Hindi"); break; - case 108: languageName = QT_TR_NOOP("Hebrew"); break; - case 109: languageName = QT_TR_NOOP("Hausa"); break; - case 110: languageName = QT_TR_NOOP("Gurani"); break; - case 111: languageName = QT_TR_NOOP("Gujurati"); break; - case 112: languageName = QT_TR_NOOP("Greek"); break; - case 113: languageName = QT_TR_NOOP("Georgian"); break; - case 114: languageName = QT_TR_NOOP("Fulani"); break; - case 115: languageName = QT_TR_NOOP("Dari"); break; - case 116: languageName = QT_TR_NOOP("Chuvash"); break; - case 117: languageName = QT_TR_NOOP("Chinese"); break; - case 118: languageName = QT_TR_NOOP("Burmese"); break; - case 119: languageName = QT_TR_NOOP("Bulgarian"); break; - case 120: languageName = QT_TR_NOOP("Bengali"); break; - case 121: languageName = QT_TR_NOOP("Belorussian"); break; - case 122: languageName = QT_TR_NOOP("Bambora"); break; - case 123: languageName = QT_TR_NOOP("Azerbaijani"); break; - case 124: languageName = QT_TR_NOOP("Assamese"); break; - case 125: languageName = QT_TR_NOOP("Armenian"); break; - case 126: languageName = QT_TR_NOOP("Arabic"); break; - case 127: languageName = QT_TR_NOOP("Amharic"); break; - default: languageName = QT_TR_NOOP("UNKNOWN"); - //std::clog << "DABConstants: Unknown language type: " - // << language << std::endl; - break; - } - - return languageName; -} - - -DABParams::DABParams(int mode) -{ - setMode(mode); -} - -void DABParams::setMode(int mode) -{ - switch (mode) - { - case 1: - dabMode = 1; - L = 76; - K = 1536; - T_F = 196608; - T_null = 2656; - T_s = 2552; - T_u = 2048; - guardLength = 504; - carrierDiff = 1000; - break; - - case 2: - dabMode = 2; - L = 76; - K = 384; - T_null = 664; - T_F = 49152; - T_s = 638; - T_u = 512; - guardLength = 126; - carrierDiff = 4000; - break; - - case 3: - dabMode = 3; - L = 153; - K = 192; - T_F = 49152; - T_null = 345; - T_s = 319; - T_u = 256; - guardLength = 63; - carrierDiff = 2000; - break; - - case 4: - dabMode = 4; - L = 76; - K = 768; - T_F = 98304; - T_null = 1328; - T_s = 1276; - T_u = 1024; - guardLength = 252; - carrierDiff = 2000; - break; - - default: - throw out_of_range("Unknown mode " + to_string(mode)); - } -} - -int Subchannel::bitrate() const -{ - const auto& ps = protectionSettings; - if (ps.shortForm) { - return ProtLevel[ps.uepTableIndex][2]; - } - else { // EEP - switch (ps.eepProfile) { - case EEPProtectionProfile::EEP_A: - switch (ps.eepLevel) { - case EEPProtectionLevel::EEP_1: - return length / 12 * 8; - case EEPProtectionLevel::EEP_2: - return length / 8 * 8; - case EEPProtectionLevel::EEP_3: - return length / 6 * 8; - case EEPProtectionLevel::EEP_4: - return length / 4 * 8; - } - break; - case EEPProtectionProfile::EEP_B: - switch (ps.eepLevel) { - case EEPProtectionLevel::EEP_1: - return length / 27 * 32; - case EEPProtectionLevel::EEP_2: - return length / 21 * 32; - case EEPProtectionLevel::EEP_3: - return length / 18 * 32; - case EEPProtectionLevel::EEP_4: - return length / 15 * 32; - } - break; - } - } - - throw std::runtime_error("Unsupported protection"); -} - -int Subchannel::numCU() const -{ - const auto& ps = protectionSettings; - if (ps.shortForm) { - return ProtLevel[ps.uepTableIndex][0]; - } - else { - switch (ps.eepProfile) { - case EEPProtectionProfile::EEP_A: - switch (ps.eepLevel) { - case EEPProtectionLevel::EEP_1: - return (bitrate() * 12) >> 3; - case EEPProtectionLevel::EEP_2: - return bitrate(); - case EEPProtectionLevel::EEP_3: - return (bitrate() * 6) >> 3; - case EEPProtectionLevel::EEP_4: - return (bitrate() >> 1); - } - break; - case EEPProtectionProfile::EEP_B: - switch (ps.eepLevel) { - case EEPProtectionLevel::EEP_1: - return (bitrate() * 27) >> 5; - case EEPProtectionLevel::EEP_2: - return (bitrate() * 21) >> 5; - case EEPProtectionLevel::EEP_3: - return (bitrate() * 18) >> 5; - case EEPProtectionLevel::EEP_4: - return (bitrate() * 15) >> 5; - } - break; - } - } - return -1; -} - -string Subchannel::protection() const -{ - string prot; - const auto& ps = protectionSettings; - if (ps.shortForm) { - prot = "UEP " + to_string((int)ps.uepLevel); - } - else { // EEP - prot = "EEP "; - switch (ps.eepProfile) { - case EEPProtectionProfile::EEP_A: - prot += to_string((int)ps.eepLevel) + "-A"; - break; - case EEPProtectionProfile::EEP_B: - prot += to_string((int)ps.eepLevel) + "-B"; - break; - } - } - return prot; -} - -TransportMode ServiceComponent::transportMode() const -{ - if (TMid == 0) { - return TransportMode::Audio; - } - else if (TMid == 1) { - return TransportMode::StreamData; - } - else if (TMid == 2) { - return TransportMode::FIDC; - } - else if (TMid == 3) { - return TransportMode::PacketData; - } - throw std::logic_error("Illegal TMid!"); -} - -AudioServiceComponentType ServiceComponent::audioType() const -{ - if (ASCTy == 0) { - return AudioServiceComponentType::DAB; - } - else if (ASCTy == 63) { - return AudioServiceComponentType::DABPlus; - } - else { - return AudioServiceComponentType::Unknown; - } -} - +/* + * Copyright (C) 2018 + * Matthias P. Braendli (matthias.braendli@mpb.li) + * + * Copyright (C) 2017 + * Albrecht Lohofener (albrechtloh@gmx.de) + * + * This file is part of the welle.io. + * Many of the ideas as implemented in welle.io are derived from + * other work, made available through the GNU general Public License. + * All copyrights of the original authors are recognized. + * + * welle.io is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * welle.io 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with welle.io; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "dab-constants.h" +#include +#include +#include +#include + +// For Qt translation if Qt is existing +#ifdef QT_CORE_LIB + #include +#else + #define QT_TR_NOOP(x) (x) +#endif + +using namespace std; + +// Table ETSI EN 300 401 Page 50 +// Table is copied from the work of Michael Hoehn +const int ProtLevel[64][3] = { + {16,5,32}, // Index 0 + {21,4,32}, + {24,3,32}, + {29,2,32}, + {35,1,32}, // Index 4 + {24,5,48}, + {29,4,48}, + {35,3,48}, + {42,2,48}, + {52,1,48}, // Index 9 + {29,5,56}, + {35,4,56}, + {42,3,56}, + {52,2,56}, + {32,5,64}, // Index 14 + {42,4,64}, + {48,3,64}, + {58,2,64}, + {70,1,64}, + {40,5,80}, // Index 19 + {52,4,80}, + {58,3,80}, + {70,2,80}, + {84,1,80}, + {48,5,96}, // Index 24 + {58,4,96}, + {70,3,96}, + {84,2,96}, + {104,1,96}, + {58,5,112}, // Index 29 + {70,4,112}, + {84,3,112}, + {104,2,112}, + {64,5,128}, + {84,4,128}, // Index 34 + {96,3,128}, + {116,2,128}, + {140,1,128}, + {80,5,160}, + {104,4,160}, // Index 39 + {116,3,160}, + {140,2,160}, + {168,1,160}, + {96,5,192}, + {116,4,192}, // Index 44 + {140,3,192}, + {168,2,192}, + {208,1,192}, + {116,5,224}, + {140,4,224}, // Index 49 + {168,3,224}, + {208,2,224}, + {232,1,224}, + {128,5,256}, + {168,4,256}, // Index 54 + {192,3,256}, + {232,2,256}, + {280,1,256}, + {160,5,320}, + {208,4,320}, // index 59 + {280,2,320}, + {192,5,384}, + {280,3,384}, + {416,1,384}}; + + +static std::string flag_to_shortlabel(const std::string& label, uint16_t flag) +{ + stringstream shortlabel; + for (size_t i = 0; i < label.size(); ++i) { + if (flag & 0x8000 >> i) { + shortlabel << label[i]; + } + } + + return shortlabel.str(); +} + +string DabLabel::utf8_label() const +{ + const auto fig2 = fig2_label(); + if (not fig2.empty()) { + return fig2; + } + else { + return fig1_label_utf8(); + } +} + +string DabLabel::fig1_label_utf8() const +{ + return toUtf8StringUsingCharset(fig1_label.c_str(), charset); +} + +string DabLabel::fig1_shortlabel_utf8() const +{ + const string shortlabel = flag_to_shortlabel(fig1_label, fig1_flag); + return toUtf8StringUsingCharset(shortlabel.c_str(), charset); +} + +void DabLabel::setCharset(uint8_t charset_id) +{ + charset = static_cast(charset_id); +} + +string DabLabel::fig2_label() const +{ + vector segments_cat; + for (size_t i = 0; i < segment_count; i++) { + if (segments.count(i) == 0) { + return ""; + } + else { + const auto& s = segments.at(i); + copy(s.begin(), s.end(), back_inserter(segments_cat)); + } + } + + switch (extended_label_charset) { + case CharacterSet::EbuLatin: + //std::clog << "DABConstants: FIG2 label encoded in EBU Latin is not allowed." << std::endl; + return ""; // Fallback to FIG1 + case CharacterSet::UnicodeUtf8: + return string(segments_cat.begin(), segments_cat.end()); + case CharacterSet::UnicodeUcs2: + return toUtf8StringUsingCharset( + segments_cat.data(), CharacterSet::UnicodeUcs2, segments_cat.size()); + case CharacterSet::Undefined: + return ""; + } + throw logic_error("invalid extended label charset " + to_string((int)extended_label_charset)); +} + +const char* DABConstants::getProgramTypeName(int type) +{ + const char* typeName = ""; + switch (type) { + case 0: typeName = ""; break; + case 1: typeName = QT_TR_NOOP("News"); break; + case 2: typeName = QT_TR_NOOP("Current Affairs"); break; + case 3: typeName = QT_TR_NOOP("Information"); break; + case 4: typeName = QT_TR_NOOP("Sport"); break; + case 5: typeName = QT_TR_NOOP("Education"); break; + case 6: typeName = QT_TR_NOOP("Drama"); break; + case 7: typeName = QT_TR_NOOP("Arts"); break; + case 8: typeName = QT_TR_NOOP("Science"); break; + case 9: typeName = QT_TR_NOOP("Talk"); break; + case 10: typeName = QT_TR_NOOP("Pop Music"); break; + case 11: typeName = QT_TR_NOOP("Rock Music"); break; + case 12: typeName = QT_TR_NOOP("Easy Listening"); break; + case 13: typeName = QT_TR_NOOP("Light classical"); break; + case 14: typeName = QT_TR_NOOP("Classical Music"); break; + case 15: typeName = QT_TR_NOOP("Other Music"); break; + case 16: typeName = QT_TR_NOOP("Weather"); break; + case 17: typeName = QT_TR_NOOP("Finance"); break; + case 18: typeName = QT_TR_NOOP("Children\'s"); break; + case 19: typeName = QT_TR_NOOP("Factual"); break; + case 20: typeName = QT_TR_NOOP("Religion"); break; + case 21: typeName = QT_TR_NOOP("Phone In"); break; + case 22: typeName = QT_TR_NOOP("Travel"); break; + case 23: typeName = QT_TR_NOOP("Leisure"); break; + case 24: typeName = QT_TR_NOOP("Jazz and Blues"); break; + case 25: typeName = QT_TR_NOOP("Country Music"); break; + case 26: typeName = QT_TR_NOOP("National Music"); break; + case 27: typeName = QT_TR_NOOP("Oldies Music"); break; + case 28: typeName = QT_TR_NOOP("Folk Music"); break; + case 29: typeName = QT_TR_NOOP("Documentary"); break; + case 30: typeName = QT_TR_NOOP("entry 30 not used"); break; + case 31: typeName = QT_TR_NOOP("entry 31 not used"); break; + default: typeName = QT_TR_NOOP("UNKNOWN"); + //std::clog << "DABConstants: Unknown program type" << std::endl; + break; + } + + return typeName; +} + +const char* DABConstants::getLanguageName(int language) +{ + const char* languageName = ""; + + switch (language) { + case 0: languageName = ""; break; + case 1: languageName = QT_TR_NOOP("Albanian"); break; + case 2: languageName = QT_TR_NOOP("Breton"); break; + case 3: languageName = QT_TR_NOOP("Catalan"); break; + case 4: languageName = QT_TR_NOOP("Croatian"); break; + case 5: languageName = QT_TR_NOOP("Welsh"); break; + case 6: languageName = QT_TR_NOOP("Czech"); break; + case 7: languageName = QT_TR_NOOP("Danish"); break; + case 8: languageName = QT_TR_NOOP("German"); break; + case 9: languageName = QT_TR_NOOP("English"); break; + case 10: languageName = QT_TR_NOOP("Spanish"); break; + case 11: languageName = QT_TR_NOOP("Esperanto"); break; + case 12: languageName = QT_TR_NOOP("Estonian"); break; + case 13: languageName = QT_TR_NOOP("Basque"); break; + case 14: languageName = QT_TR_NOOP("Faroese"); break; + case 15: languageName = QT_TR_NOOP("French"); break; + case 16: languageName = QT_TR_NOOP("Frisian"); break; + case 17: languageName = QT_TR_NOOP("Irish"); break; + case 18: languageName = QT_TR_NOOP("Gaelic"); break; + case 19: languageName = QT_TR_NOOP("Galician"); break; + case 20: languageName = QT_TR_NOOP("Icelandic"); break; + case 21: languageName = QT_TR_NOOP("Italian"); break; + case 22: languageName = QT_TR_NOOP("Lappish"); break; + case 23: languageName = QT_TR_NOOP("Latin"); break; + case 24: languageName = QT_TR_NOOP("Latvian"); break; + case 25: languageName = QT_TR_NOOP("Luxembourgian"); break; + case 26: languageName = QT_TR_NOOP("Lithuanian"); break; + case 27: languageName = QT_TR_NOOP("Hungarian"); break; + case 28: languageName = QT_TR_NOOP("Maltese"); break; + case 29: languageName = QT_TR_NOOP("Dutch"); break; + case 30: languageName = QT_TR_NOOP("Norwegian"); break; + case 31: languageName = QT_TR_NOOP("Occitan"); break; + case 32: languageName = QT_TR_NOOP("Polish"); break; + case 33: languageName = QT_TR_NOOP("Portuguese"); break; + case 34: languageName = QT_TR_NOOP("Romanian"); break; + case 35: languageName = QT_TR_NOOP("Romansh"); break; + case 36: languageName = QT_TR_NOOP("Serbian"); break; + case 37: languageName = QT_TR_NOOP("Slovak"); break; + case 38: languageName = QT_TR_NOOP("Slovene"); break; + case 39: languageName = QT_TR_NOOP("Finnish"); break; + case 40: languageName = QT_TR_NOOP("Swedish"); break; + case 41: languageName = QT_TR_NOOP("Turkish"); break; + case 42: languageName = QT_TR_NOOP("Flemish"); break; + case 43: languageName = QT_TR_NOOP("Walloon"); break; + case 64: languageName = QT_TR_NOOP("Background sound/clean feed"); break; + case 69: languageName = QT_TR_NOOP("Zulu"); break; + case 70: languageName = QT_TR_NOOP("Vietnamese"); break; + case 71: languageName = QT_TR_NOOP("Uzbek"); break; + case 72: languageName = QT_TR_NOOP("Urdu"); break; + case 73: languageName = QT_TR_NOOP("Ukrainian"); break; + case 74: languageName = QT_TR_NOOP("Thai"); break; + case 75: languageName = QT_TR_NOOP("Telugu"); break; + case 76: languageName = QT_TR_NOOP("Tatar"); break; + case 77: languageName = QT_TR_NOOP("Tamil"); break; + case 78: languageName = QT_TR_NOOP("Tadzhik"); break; + case 79: languageName = QT_TR_NOOP("Swahili"); break; + case 80: languageName = QT_TR_NOOP("Sranan Tongo"); break; + case 81: languageName = QT_TR_NOOP("Somali"); break; + case 82: languageName = QT_TR_NOOP("Sinhalese"); break; + case 83: languageName = QT_TR_NOOP("Shona"); break; + case 84: languageName = QT_TR_NOOP("Serbo-Croat"); break; + case 85: languageName = QT_TR_NOOP("Rusyn"); break; + case 86: languageName = QT_TR_NOOP("Russian"); break; + case 87: languageName = QT_TR_NOOP("Quechua"); break; + case 88: languageName = QT_TR_NOOP("Pushtu"); break; + case 89: languageName = QT_TR_NOOP("Punjabi"); break; + case 90: languageName = QT_TR_NOOP("Persian"); break; + case 91: languageName = QT_TR_NOOP("Papiamento"); break; + case 92: languageName = QT_TR_NOOP("Oriya"); break; + case 93: languageName = QT_TR_NOOP("Nepali"); break; + case 94: languageName = QT_TR_NOOP("Ndebele"); break; + case 95: languageName = QT_TR_NOOP("Marathi"); break; + case 96: languageName = QT_TR_NOOP("Moldavian"); break; + case 97: languageName = QT_TR_NOOP("Malaysian"); break; + case 98: languageName = QT_TR_NOOP("Malagasay"); break; + case 99: languageName = QT_TR_NOOP("Macedonian"); break; + case 100: languageName = QT_TR_NOOP("Laotian"); break; + case 101: languageName = QT_TR_NOOP("Korean"); break; + case 102: languageName = QT_TR_NOOP("Khmer"); break; + case 103: languageName = QT_TR_NOOP("Kazakh"); break; + case 104: languageName = QT_TR_NOOP("Kannada"); break; + case 105: languageName = QT_TR_NOOP("Japanese"); break; + case 106: languageName = QT_TR_NOOP("Indonesian"); break; + case 107: languageName = QT_TR_NOOP("Hindi"); break; + case 108: languageName = QT_TR_NOOP("Hebrew"); break; + case 109: languageName = QT_TR_NOOP("Hausa"); break; + case 110: languageName = QT_TR_NOOP("Gurani"); break; + case 111: languageName = QT_TR_NOOP("Gujurati"); break; + case 112: languageName = QT_TR_NOOP("Greek"); break; + case 113: languageName = QT_TR_NOOP("Georgian"); break; + case 114: languageName = QT_TR_NOOP("Fulani"); break; + case 115: languageName = QT_TR_NOOP("Dari"); break; + case 116: languageName = QT_TR_NOOP("Chuvash"); break; + case 117: languageName = QT_TR_NOOP("Chinese"); break; + case 118: languageName = QT_TR_NOOP("Burmese"); break; + case 119: languageName = QT_TR_NOOP("Bulgarian"); break; + case 120: languageName = QT_TR_NOOP("Bengali"); break; + case 121: languageName = QT_TR_NOOP("Belorussian"); break; + case 122: languageName = QT_TR_NOOP("Bambora"); break; + case 123: languageName = QT_TR_NOOP("Azerbaijani"); break; + case 124: languageName = QT_TR_NOOP("Assamese"); break; + case 125: languageName = QT_TR_NOOP("Armenian"); break; + case 126: languageName = QT_TR_NOOP("Arabic"); break; + case 127: languageName = QT_TR_NOOP("Amharic"); break; + default: languageName = QT_TR_NOOP("UNKNOWN"); + //std::clog << "DABConstants: Unknown language type: " + // << language << std::endl; + break; + } + + return languageName; +} + + +DABParams::DABParams(int mode) +{ + setMode(mode); +} + +void DABParams::setMode(int mode) +{ + switch (mode) + { + case 1: + dabMode = 1; + L = 76; + K = 1536; + T_F = 196608; + T_null = 2656; + T_s = 2552; + T_u = 2048; + guardLength = 504; + carrierDiff = 1000; + break; + + case 2: + dabMode = 2; + L = 76; + K = 384; + T_null = 664; + T_F = 49152; + T_s = 638; + T_u = 512; + guardLength = 126; + carrierDiff = 4000; + break; + + case 3: + dabMode = 3; + L = 153; + K = 192; + T_F = 49152; + T_null = 345; + T_s = 319; + T_u = 256; + guardLength = 63; + carrierDiff = 2000; + break; + + case 4: + dabMode = 4; + L = 76; + K = 768; + T_F = 98304; + T_null = 1328; + T_s = 1276; + T_u = 1024; + guardLength = 252; + carrierDiff = 2000; + break; + + default: + throw out_of_range("Unknown mode " + to_string(mode)); + } +} + +int Subchannel::bitrate() const +{ + const auto& ps = protectionSettings; + if (ps.shortForm) { + return ProtLevel[ps.uepTableIndex][2]; + } + else { // EEP + switch (ps.eepProfile) { + case EEPProtectionProfile::EEP_A: + switch (ps.eepLevel) { + case EEPProtectionLevel::EEP_1: + return length / 12 * 8; + case EEPProtectionLevel::EEP_2: + return length / 8 * 8; + case EEPProtectionLevel::EEP_3: + return length / 6 * 8; + case EEPProtectionLevel::EEP_4: + return length / 4 * 8; + } + break; + case EEPProtectionProfile::EEP_B: + switch (ps.eepLevel) { + case EEPProtectionLevel::EEP_1: + return length / 27 * 32; + case EEPProtectionLevel::EEP_2: + return length / 21 * 32; + case EEPProtectionLevel::EEP_3: + return length / 18 * 32; + case EEPProtectionLevel::EEP_4: + return length / 15 * 32; + } + break; + } + } + + throw std::runtime_error("Unsupported protection"); +} + +int Subchannel::numCU() const +{ + const auto& ps = protectionSettings; + if (ps.shortForm) { + return ProtLevel[ps.uepTableIndex][0]; + } + else { + switch (ps.eepProfile) { + case EEPProtectionProfile::EEP_A: + switch (ps.eepLevel) { + case EEPProtectionLevel::EEP_1: + return (bitrate() * 12) >> 3; + case EEPProtectionLevel::EEP_2: + return bitrate(); + case EEPProtectionLevel::EEP_3: + return (bitrate() * 6) >> 3; + case EEPProtectionLevel::EEP_4: + return (bitrate() >> 1); + } + break; + case EEPProtectionProfile::EEP_B: + switch (ps.eepLevel) { + case EEPProtectionLevel::EEP_1: + return (bitrate() * 27) >> 5; + case EEPProtectionLevel::EEP_2: + return (bitrate() * 21) >> 5; + case EEPProtectionLevel::EEP_3: + return (bitrate() * 18) >> 5; + case EEPProtectionLevel::EEP_4: + return (bitrate() * 15) >> 5; + } + break; + } + } + return -1; +} + +string Subchannel::protection() const +{ + string prot; + const auto& ps = protectionSettings; + if (ps.shortForm) { + prot = "UEP " + to_string((int)ps.uepLevel); + } + else { // EEP + prot = "EEP "; + switch (ps.eepProfile) { + case EEPProtectionProfile::EEP_A: + prot += to_string((int)ps.eepLevel) + "-A"; + break; + case EEPProtectionProfile::EEP_B: + prot += to_string((int)ps.eepLevel) + "-B"; + break; + } + } + return prot; +} + +TransportMode ServiceComponent::transportMode() const +{ + if (TMid == 0) { + return TransportMode::Audio; + } + else if (TMid == 1) { + return TransportMode::StreamData; + } + else if (TMid == 2) { + return TransportMode::FIDC; + } + else if (TMid == 3) { + return TransportMode::PacketData; + } + throw std::logic_error("Illegal TMid!"); +} + +AudioServiceComponentType ServiceComponent::audioType() const +{ + if (ASCTy == 0) { + return AudioServiceComponentType::DAB; + } + else if (ASCTy == 63) { + return AudioServiceComponentType::DABPlus; + } + else { + return AudioServiceComponentType::Unknown; + } +} + diff --git a/src/dabdsp/dab-constants.h b/src/dabdsp/dab-constants.h index 97b4075..4f55d6b 100644 --- a/src/dabdsp/dab-constants.h +++ b/src/dabdsp/dab-constants.h @@ -1,249 +1,249 @@ -/* - * Copyright (C) 2018 - * Matthias P. Braendli (matthias.braendli@mpb.li) - * - * Copyright (C) 2017 - * Albrecht Lohofener (albrechtloh@gmx.de) - * - * This file is based on SDR-J - * Copyright (C) 2010, 2011, 2012 - * Jan van Katwijk (J.vanKatwijk@gmail.com) - * - * This file is part of the welle.io. - * Many of the ideas as implemented in welle.io are derived from - * other work, made available through the GNU general Public License. - * All copyrights of the original authors are recognized. - * - * welle.io is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * welle.io 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with welle.io; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ -// Common definitions and includes for the DAB decoder - -#ifndef __DAB_CONSTANTS -#define __DAB_CONSTANTS - -#include "charsets.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using DSPFLOAT = float; -using DSPCOMPLEX = std::complex; -using softbit_t = int8_t; - -struct aligned_deleter_t -{ - template - void operator()(T* ptr) - { - if(ptr) { - - ptr->~T(); -#ifdef _MSC_VER - _aligned_free(ptr); -#else - free(ptr); -#endif - } - } -}; - -template -using aligned_ptr = std::unique_ptr; - -template -inline aligned_ptr make_aligned(Args&&... args) -{ -#ifdef _MSC_VER - auto ptr = _aligned_malloc(sizeof(T), alignof(T)); - if(ptr == nullptr) throw std::bad_alloc(); -#else - void* ptr = nullptr; - if(posix_memalign(&ptr, alignof(T), sizeof(T)) != 0) throw std::bad_alloc(); -#endif - - try { - - auto instance = ::new(ptr) T(std::forward(args)...); - return aligned_ptr(instance); - } - catch(...) { - -#ifdef _MSC_VER - if(ptr != nullptr) _aligned_free(ptr); -#else - if(ptr != nullptr) free(ptr); -#endif - throw; - } -} - -enum class AudioServiceComponentType { DAB, DABPlus, Unknown }; - -enum class TransportMode { Audio, StreamData, FIDC, PacketData }; - -#define INPUT_RATE 2048000 -#define BANDWIDTH 1536000 - -#define SYNCED 01 -#define LONG_HIGH 02 -#define LONG_LOW 03 -#define UNSYNCED 04 - -namespace DABConstants { - const char* getProgramTypeName(int type); - const char* getLanguageName(int language); -} - -extern const int ProtLevel[64][3]; - -class DABParams { -public: - DABParams(int mode); - void setMode(int mode); - - // To access directly the members is ugly but it was the easiest for the existing code - uint8_t dabMode; - int16_t L; // symbols per transmission frame - int16_t K; // Number of FFT carriers with power - int16_t T_null; // null symbol length - int32_t T_F; // samples per transmission frame - int16_t T_s; // symbol length including cyclic prefix - int16_t T_u; // Size of the FFT == symbol length without cyclic prefix - int16_t guardLength; - int16_t carrierDiff; -}; - -struct DabLabel { - // Label from FIG 1 - /* FIG 1 labels are usually in EBU Latin encoded */ - CharacterSet charset = CharacterSet::EbuLatin; - std::string fig1_label; // encoded according to charset - uint16_t fig1_flag = 0x0000; // describes the short label - - /* If necessary, convert the label to UTF8 */ - std::string fig1_label_utf8() const; - std::string fig1_shortlabel_utf8() const; - - void setCharset(uint8_t charset_id); - - // Extended Label from FIG 2 - /* FIG 2 labels are either in UTF-8 or UCS2. We store them as segments and build - * a UTF-8 string when needed. */ - - std::map > segments; - size_t segment_count = 0; // number if actual segments (not segment count as in spec) - CharacterSet extended_label_charset = CharacterSet::Undefined; - uint8_t toggle_flag = 0; - bool fig2_rfu = false; // draftETSI TS 103 176 v2.2.1 gives this a new meaning - - // Assemble all segments into a UTF-8 string. Returns an - // empty string if not all segments received. - std::string fig2_label() const; - - // Common to FIG 1 and FIG 2 - /* If FIG 2 label available, use that one, otherwise take the FIG 1 label */ - std::string utf8_label() const; -}; - -struct Service { - Service(uint32_t sid) : serviceId(sid) {} - - uint32_t serviceId = 0; - - DabLabel serviceLabel; - int16_t language = 0; - int16_t programType = 0; // PTy, FIG0/17 -}; - -// The service component describes the actual service -// It really should be a union -struct ServiceComponent { - int8_t TMid = 0; // the transport mode - uint32_t SId = 0; // belongs to the service - int16_t componentNr = 0; // component - - DabLabel componentLabel; - - int16_t ASCTy = 0; // used for audio - int16_t PS_flag = 0; // use for both audio and packet - int16_t subchannelId = 0; // used in both audio and packet - uint16_t SCId = 0; // used in packet - uint8_t CAflag = 0; // used in packet (or not at all) - int16_t DSCTy = 0; // used in packet - uint8_t DGflag = 0; // used for TDC - int16_t packetAddress = 0; // used in packet - - TransportMode transportMode(void) const; - AudioServiceComponentType audioType(void) const; -}; - -enum class EEPProtectionProfile { - EEP_A, - EEP_B, -}; - -enum class EEPProtectionLevel { - EEP_1 = 1, - EEP_2 = 2, - EEP_3 = 3, - EEP_4 = 4, -}; - -struct ProtectionSettings { - bool shortForm = false; - - // when short-form, UEP: - int16_t uepTableIndex = 0; - int16_t uepLevel = 0; - - // when long-form, EEP: - EEPProtectionProfile eepProfile = EEPProtectionProfile::EEP_A; - EEPProtectionLevel eepLevel = EEPProtectionLevel::EEP_3; -}; - -struct Subchannel { - int32_t subChId = -1; - int32_t startAddr = 0; - int32_t length = 0; - bool programmeNotData = true; - - ProtectionSettings protectionSettings; - - int16_t language = 0; - - // For subchannels carrying packet-mode service components - int16_t fecScheme = 0; // 0=no FEC, 1=FEC, 2=Rfu, 3=Rfu - - // Calculate the effective subchannel bitrate - int bitrate(void) const; - - // Calculate number of CUs this subchannel consumes - int numCU(void) const; - - std::string protection(void) const; - - inline bool valid() const { return subChId != -1; } -}; - -#endif +/* + * Copyright (C) 2018 + * Matthias P. Braendli (matthias.braendli@mpb.li) + * + * Copyright (C) 2017 + * Albrecht Lohofener (albrechtloh@gmx.de) + * + * This file is based on SDR-J + * Copyright (C) 2010, 2011, 2012 + * Jan van Katwijk (J.vanKatwijk@gmail.com) + * + * This file is part of the welle.io. + * Many of the ideas as implemented in welle.io are derived from + * other work, made available through the GNU general Public License. + * All copyrights of the original authors are recognized. + * + * welle.io is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * welle.io 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with welle.io; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +// Common definitions and includes for the DAB decoder + +#ifndef __DAB_CONSTANTS +#define __DAB_CONSTANTS + +#include "charsets.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using DSPFLOAT = float; +using DSPCOMPLEX = std::complex; +using softbit_t = int8_t; + +struct aligned_deleter_t +{ + template + void operator()(T* ptr) + { + if(ptr) { + + ptr->~T(); +#ifdef _MSC_VER + _aligned_free(ptr); +#else + free(ptr); +#endif + } + } +}; + +template +using aligned_ptr = std::unique_ptr; + +template +inline aligned_ptr make_aligned(Args&&... args) +{ +#ifdef _MSC_VER + auto ptr = _aligned_malloc(sizeof(T), alignof(T)); + if(ptr == nullptr) throw std::bad_alloc(); +#else + void* ptr = nullptr; + if(posix_memalign(&ptr, alignof(T), sizeof(T)) != 0) throw std::bad_alloc(); +#endif + + try { + + auto instance = ::new(ptr) T(std::forward(args)...); + return aligned_ptr(instance); + } + catch(...) { + +#ifdef _MSC_VER + if(ptr != nullptr) _aligned_free(ptr); +#else + if(ptr != nullptr) free(ptr); +#endif + throw; + } +} + +enum class AudioServiceComponentType { DAB, DABPlus, Unknown }; + +enum class TransportMode { Audio, StreamData, FIDC, PacketData }; + +#define INPUT_RATE 2048000 +#define BANDWIDTH 1536000 + +#define SYNCED 01 +#define LONG_HIGH 02 +#define LONG_LOW 03 +#define UNSYNCED 04 + +namespace DABConstants { + const char* getProgramTypeName(int type); + const char* getLanguageName(int language); +} + +extern const int ProtLevel[64][3]; + +class DABParams { +public: + DABParams(int mode); + void setMode(int mode); + + // To access directly the members is ugly but it was the easiest for the existing code + uint8_t dabMode; + int16_t L; // symbols per transmission frame + int16_t K; // Number of FFT carriers with power + int16_t T_null; // null symbol length + int32_t T_F; // samples per transmission frame + int16_t T_s; // symbol length including cyclic prefix + int16_t T_u; // Size of the FFT == symbol length without cyclic prefix + int16_t guardLength; + int16_t carrierDiff; +}; + +struct DabLabel { + // Label from FIG 1 + /* FIG 1 labels are usually in EBU Latin encoded */ + CharacterSet charset = CharacterSet::EbuLatin; + std::string fig1_label; // encoded according to charset + uint16_t fig1_flag = 0x0000; // describes the short label + + /* If necessary, convert the label to UTF8 */ + std::string fig1_label_utf8() const; + std::string fig1_shortlabel_utf8() const; + + void setCharset(uint8_t charset_id); + + // Extended Label from FIG 2 + /* FIG 2 labels are either in UTF-8 or UCS2. We store them as segments and build + * a UTF-8 string when needed. */ + + std::map > segments; + size_t segment_count = 0; // number if actual segments (not segment count as in spec) + CharacterSet extended_label_charset = CharacterSet::Undefined; + uint8_t toggle_flag = 0; + bool fig2_rfu = false; // draftETSI TS 103 176 v2.2.1 gives this a new meaning + + // Assemble all segments into a UTF-8 string. Returns an + // empty string if not all segments received. + std::string fig2_label() const; + + // Common to FIG 1 and FIG 2 + /* If FIG 2 label available, use that one, otherwise take the FIG 1 label */ + std::string utf8_label() const; +}; + +struct Service { + Service(uint32_t sid) : serviceId(sid) {} + + uint32_t serviceId = 0; + + DabLabel serviceLabel; + int16_t language = 0; + int16_t programType = 0; // PTy, FIG0/17 +}; + +// The service component describes the actual service +// It really should be a union +struct ServiceComponent { + int8_t TMid = 0; // the transport mode + uint32_t SId = 0; // belongs to the service + int16_t componentNr = 0; // component + + DabLabel componentLabel; + + int16_t ASCTy = 0; // used for audio + int16_t PS_flag = 0; // use for both audio and packet + int16_t subchannelId = 0; // used in both audio and packet + uint16_t SCId = 0; // used in packet + uint8_t CAflag = 0; // used in packet (or not at all) + int16_t DSCTy = 0; // used in packet + uint8_t DGflag = 0; // used for TDC + int16_t packetAddress = 0; // used in packet + + TransportMode transportMode(void) const; + AudioServiceComponentType audioType(void) const; +}; + +enum class EEPProtectionProfile { + EEP_A, + EEP_B, +}; + +enum class EEPProtectionLevel { + EEP_1 = 1, + EEP_2 = 2, + EEP_3 = 3, + EEP_4 = 4, +}; + +struct ProtectionSettings { + bool shortForm = false; + + // when short-form, UEP: + int16_t uepTableIndex = 0; + int16_t uepLevel = 0; + + // when long-form, EEP: + EEPProtectionProfile eepProfile = EEPProtectionProfile::EEP_A; + EEPProtectionLevel eepLevel = EEPProtectionLevel::EEP_3; +}; + +struct Subchannel { + int32_t subChId = -1; + int32_t startAddr = 0; + int32_t length = 0; + bool programmeNotData = true; + + ProtectionSettings protectionSettings; + + int16_t language = 0; + + // For subchannels carrying packet-mode service components + int16_t fecScheme = 0; // 0=no FEC, 1=FEC, 2=Rfu, 3=Rfu + + // Calculate the effective subchannel bitrate + int bitrate(void) const; + + // Calculate number of CUs this subchannel consumes + int numCU(void) const; + + std::string protection(void) const; + + inline bool valid() const { return subChId != -1; } +}; + +#endif diff --git a/src/dabdsp/dab-processor.h b/src/dabdsp/dab-processor.h index 9f6760f..db187cc 100644 --- a/src/dabdsp/dab-processor.h +++ b/src/dabdsp/dab-processor.h @@ -1,38 +1,38 @@ -/* - * - * Copyright (C) 2013 - * Jan van Katwijk (J.vanKatwijk@gmail.com) - * Lazy Chair Programming - * - * This file is part of the SDR-J (JSDR). - * SDR-J is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * SDR-J 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with SDR-J; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ -#ifndef DAB_PROCESSOR -#define DAB_PROCESSOR - -#include -#include - -// virtual class, just for providing a common base -// for the real decoder classes -class DabProcessor { - public: - virtual ~DabProcessor() = default; - virtual void addtoFrame(uint8_t *) = 0; -}; - -#endif - +/* + * + * Copyright (C) 2013 + * Jan van Katwijk (J.vanKatwijk@gmail.com) + * Lazy Chair Programming + * + * This file is part of the SDR-J (JSDR). + * SDR-J is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * SDR-J 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with SDR-J; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef DAB_PROCESSOR +#define DAB_PROCESSOR + +#include +#include + +// virtual class, just for providing a common base +// for the real decoder classes +class DabProcessor { + public: + virtual ~DabProcessor() = default; + virtual void addtoFrame(uint8_t *) = 0; +}; + +#endif + diff --git a/src/dabdsp/dab-virtual.h b/src/dabdsp/dab-virtual.h index 6dc20a5..04b39e8 100644 --- a/src/dabdsp/dab-virtual.h +++ b/src/dabdsp/dab-virtual.h @@ -1,37 +1,37 @@ -/* - * Copyright (C) 2013 - * Jan van Katwijk (J.vanKatwijk@gmail.com) - * Lazy Chair Programming - * - * This file is part of the SDR-J (JSDR). - * SDR-J is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * SDR-J 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with SDR-J; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ -#ifndef _DAB_VIRTUAL -#define _DAB_VIRTUAL - -#include -#include "dab-constants.h" - -#define CUSize (4 * 16) - -class DabVirtual { - public: - virtual ~DabVirtual() {} - virtual int32_t process(const softbit_t *v, int16_t cnt) = 0; -}; -#endif - - +/* + * Copyright (C) 2013 + * Jan van Katwijk (J.vanKatwijk@gmail.com) + * Lazy Chair Programming + * + * This file is part of the SDR-J (JSDR). + * SDR-J is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * SDR-J 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with SDR-J; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef _DAB_VIRTUAL +#define _DAB_VIRTUAL + +#include +#include "dab-constants.h" + +#define CUSize (4 * 16) + +class DabVirtual { + public: + virtual ~DabVirtual() {} + virtual int32_t process(const softbit_t *v, int16_t cnt) = 0; +}; +#endif + + diff --git a/src/dabdsp/decode_rs.h b/src/dabdsp/decode_rs.h index 7443205..936791b 100644 --- a/src/dabdsp/decode_rs.h +++ b/src/dabdsp/decode_rs.h @@ -1,312 +1,312 @@ -/* The guts of the Reed-Solomon decoder, meant to be #included - * into a function body with the following typedefs, macros and variables supplied - * according to the code parameters: - - * data_t - a typedef for the data symbol - * data_t data[] - array of NN data and parity symbols to be corrected in place - * retval - an integer lvalue into which the decoder's return code is written - * NROOTS - the number of roots in the RS code generator polynomial, - * which is the same as the number of parity symbols in a block. - Integer variable or literal. - * NN - the total number of symbols in a RS block. Integer variable or literal. - * PAD - the number of pad symbols in a block. Integer variable or literal. - * ALPHA_TO - The address of an array of NN elements to convert Galois field - * elements in index (log) form to polynomial form. Read only. - * INDEX_OF - The address of an array of NN elements to convert Galois field - * elements in polynomial form to index (log) form. Read only. - * MODNN - a function to reduce its argument modulo NN. May be inline or a macro. - * FCR - An integer literal or variable specifying the first consecutive root of the - * Reed-Solomon generator polynomial. Integer variable or literal. - * PRIM - The primitive root of the generator poly. Integer variable or literal. - * DEBUG - If set to 1 or more, do various internal consistency checking. Leave this - * undefined for production code - - * The memset(), memmove(), and memcpy() functions are used. The appropriate header - * file declaring these functions (usually ) must be included by the calling - * program. - */ - - -#if !defined(NROOTS) -#error "NROOTS not defined" -#endif - -#if !defined(NN) -#error "NN not defined" -#endif - -#if !defined(PAD) -#error "PAD not defined" -#endif - -#if !defined(ALPHA_TO) -#error "ALPHA_TO not defined" -#endif - -#if !defined(INDEX_OF) -#error "INDEX_OF not defined" -#endif - -#if !defined(MODNN) -#error "MODNN not defined" -#endif - -#if !defined(FCR) -#error "FCR not defined" -#endif - -#if !defined(PRIM) -#error "PRIM not defined" -#endif - -#if !defined(NULL) -#define NULL ((void *)0) -#endif - -#undef MIN -#define MIN(a,b) ((a) < (b) ? (a) : (b)) -#undef A0 -#define A0 (NN) - -{ - int deg_lambda, el, deg_omega; - int i, j, r,k; - data_t u,q,tmp,num1,num2,den,discr_r; - data_t* lambda = malloc(sizeof(data_t) * (NROOTS + 1)); - data_t* s = malloc(sizeof(data_t) * NROOTS); - data_t* b = malloc(sizeof(data_t) * (NROOTS + 1)); - data_t* t = malloc(sizeof(data_t) * (NROOTS + 1)); - data_t* omega = malloc(sizeof(data_t) * (NROOTS + 1)); - data_t* root = malloc(sizeof(data_t) * NROOTS); - data_t* reg = malloc(sizeof(data_t) * (NROOTS + 1)); - data_t* loc = malloc(sizeof(data_t) * NROOTS); - int syn_error, count; - - /* form the syndromes; i.e., evaluate data(x) at roots of g(x) */ - for(i=0;i 0) { - /* Init lambda to be the erasure locator polynomial */ - lambda[1] = ALPHA_TO[MODNN(PRIM*(NN-1-eras_pos[0]))]; - for (i = 1; i < no_eras; i++) { - u = MODNN(PRIM*(NN-1-eras_pos[i])); - for (j = i+1; j > 0; j--) { - tmp = INDEX_OF[lambda[j - 1]]; - if(tmp != A0) - lambda[j] ^= ALPHA_TO[MODNN(u + tmp)]; - } - } - -#if DEBUG >= 1 - /* Test code that verifies the erasure locator polynomial just constructed - Needed only for decoder debugging. */ - - /* find roots of the erasure location polynomial */ - for(i=1;i<=no_eras;i++) - reg[i] = INDEX_OF[lambda[i]]; - - count = 0; - for (i = 1,k=IPRIM-1; i <= NN; i++,k = MODNN(k+IPRIM)) { - q = 1; - for (j = 1; j <= no_eras; j++) - if (reg[j] != A0) { - reg[j] = MODNN(reg[j] + j); - q ^= ALPHA_TO[reg[j]]; - } - if (q != 0) - continue; - /* store root and error location number indices */ - root[count] = i; - loc[count] = k; - count++; - } - if (count != no_eras) { - fprintf(stderr, "count = %d no_eras = %d\n lambda(x) is WRONG\n",count,no_eras); - count = -1; - goto finish; - } -#if DEBUG >= 2 - fprintf(stderr, "\n Erasure positions as determined by roots of Eras Loc Poly:\n"); - for (i = 0; i < count; i++) - fprintf(stderr, "%d ", loc[i]); - fprintf(stderr, "\n"); -#endif -#endif - } - for(i=0;i 0; j--){ - if (reg[j] != A0) { - reg[j] = MODNN(reg[j] + j); - q ^= ALPHA_TO[reg[j]]; - } - } - if (q != 0) - continue; /* Not a root */ - /* store root (index-form) and error location number */ -#if DEBUG>=2 - fprintf(stderr, "count %d root %d loc %d\n",count,i,k); -#endif - root[count] = i; - loc[count] = k; - /* If we've already found max possible roots, - * abort the search to save time - */ - if(++count == deg_lambda) - break; - } - if (deg_lambda != count) { - /* - * deg(lambda) unequal to number of roots => uncorrectable - * error detected - */ - count = -1; - goto finish; - } - /* - * Compute err+eras evaluator poly omega(x) = s(x)*lambda(x) (modulo - * x**NROOTS). in index form. Also find deg(omega). - */ - deg_omega = deg_lambda-1; - for (i = 0; i <= deg_omega;i++){ - tmp = 0; - for(j=i;j >= 0; j--){ - if ((s[i - j] != A0) && (lambda[j] != A0)) - tmp ^= ALPHA_TO[MODNN(s[i - j] + lambda[j])]; - } - omega[i] = INDEX_OF[tmp]; - } - - /* - * Compute error values in poly-form. num1 = omega(inv(X(l))), num2 = - * inv(X(l))**(FCR-1) and den = lambda_pr(inv(X(l))) all in poly-form - */ - for (j = count-1; j >=0; j--) { - num1 = 0; - for (i = deg_omega; i >= 0; i--) { - if (omega[i] != A0) - num1 ^= ALPHA_TO[MODNN(omega[i] + i * root[j])]; - } - num2 = ALPHA_TO[MODNN(root[j] * (FCR - 1) + NN)]; - den = 0; - - /* lambda[i+1] for i even is the formal derivative lambda_pr of lambda[i] */ - for (i = MIN(deg_lambda,NROOTS-1) & ~1; i >= 0; i -=2) { - if(lambda[i+1] != A0) - den ^= ALPHA_TO[MODNN(lambda[i+1] + i * root[j])]; - } -#if DEBUG >= 1 - if (den == 0) { - fprintf(stderr, "\n ERROR: denominator = 0\n"); - count = -1; - goto finish; - } -#endif - /* Apply error to data */ - if (num1 != 0 && loc[j] >= PAD) { - data[loc[j]-PAD] ^= ALPHA_TO[MODNN(INDEX_OF[num1] + INDEX_OF[num2] + NN - INDEX_OF[den])]; - } - } - finish: - if(eras_pos != NULL){ - for(i=0;i) must be included by the calling + * program. + */ + + +#if !defined(NROOTS) +#error "NROOTS not defined" +#endif + +#if !defined(NN) +#error "NN not defined" +#endif + +#if !defined(PAD) +#error "PAD not defined" +#endif + +#if !defined(ALPHA_TO) +#error "ALPHA_TO not defined" +#endif + +#if !defined(INDEX_OF) +#error "INDEX_OF not defined" +#endif + +#if !defined(MODNN) +#error "MODNN not defined" +#endif + +#if !defined(FCR) +#error "FCR not defined" +#endif + +#if !defined(PRIM) +#error "PRIM not defined" +#endif + +#if !defined(NULL) +#define NULL ((void *)0) +#endif + +#undef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#undef A0 +#define A0 (NN) + +{ + int deg_lambda, el, deg_omega; + int i, j, r,k; + data_t u,q,tmp,num1,num2,den,discr_r; + data_t* lambda = malloc(sizeof(data_t) * (NROOTS + 1)); + data_t* s = malloc(sizeof(data_t) * NROOTS); + data_t* b = malloc(sizeof(data_t) * (NROOTS + 1)); + data_t* t = malloc(sizeof(data_t) * (NROOTS + 1)); + data_t* omega = malloc(sizeof(data_t) * (NROOTS + 1)); + data_t* root = malloc(sizeof(data_t) * NROOTS); + data_t* reg = malloc(sizeof(data_t) * (NROOTS + 1)); + data_t* loc = malloc(sizeof(data_t) * NROOTS); + int syn_error, count; + + /* form the syndromes; i.e., evaluate data(x) at roots of g(x) */ + for(i=0;i 0) { + /* Init lambda to be the erasure locator polynomial */ + lambda[1] = ALPHA_TO[MODNN(PRIM*(NN-1-eras_pos[0]))]; + for (i = 1; i < no_eras; i++) { + u = MODNN(PRIM*(NN-1-eras_pos[i])); + for (j = i+1; j > 0; j--) { + tmp = INDEX_OF[lambda[j - 1]]; + if(tmp != A0) + lambda[j] ^= ALPHA_TO[MODNN(u + tmp)]; + } + } + +#if DEBUG >= 1 + /* Test code that verifies the erasure locator polynomial just constructed + Needed only for decoder debugging. */ + + /* find roots of the erasure location polynomial */ + for(i=1;i<=no_eras;i++) + reg[i] = INDEX_OF[lambda[i]]; + + count = 0; + for (i = 1,k=IPRIM-1; i <= NN; i++,k = MODNN(k+IPRIM)) { + q = 1; + for (j = 1; j <= no_eras; j++) + if (reg[j] != A0) { + reg[j] = MODNN(reg[j] + j); + q ^= ALPHA_TO[reg[j]]; + } + if (q != 0) + continue; + /* store root and error location number indices */ + root[count] = i; + loc[count] = k; + count++; + } + if (count != no_eras) { + fprintf(stderr, "count = %d no_eras = %d\n lambda(x) is WRONG\n",count,no_eras); + count = -1; + goto finish; + } +#if DEBUG >= 2 + fprintf(stderr, "\n Erasure positions as determined by roots of Eras Loc Poly:\n"); + for (i = 0; i < count; i++) + fprintf(stderr, "%d ", loc[i]); + fprintf(stderr, "\n"); +#endif +#endif + } + for(i=0;i 0; j--){ + if (reg[j] != A0) { + reg[j] = MODNN(reg[j] + j); + q ^= ALPHA_TO[reg[j]]; + } + } + if (q != 0) + continue; /* Not a root */ + /* store root (index-form) and error location number */ +#if DEBUG>=2 + fprintf(stderr, "count %d root %d loc %d\n",count,i,k); +#endif + root[count] = i; + loc[count] = k; + /* If we've already found max possible roots, + * abort the search to save time + */ + if(++count == deg_lambda) + break; + } + if (deg_lambda != count) { + /* + * deg(lambda) unequal to number of roots => uncorrectable + * error detected + */ + count = -1; + goto finish; + } + /* + * Compute err+eras evaluator poly omega(x) = s(x)*lambda(x) (modulo + * x**NROOTS). in index form. Also find deg(omega). + */ + deg_omega = deg_lambda-1; + for (i = 0; i <= deg_omega;i++){ + tmp = 0; + for(j=i;j >= 0; j--){ + if ((s[i - j] != A0) && (lambda[j] != A0)) + tmp ^= ALPHA_TO[MODNN(s[i - j] + lambda[j])]; + } + omega[i] = INDEX_OF[tmp]; + } + + /* + * Compute error values in poly-form. num1 = omega(inv(X(l))), num2 = + * inv(X(l))**(FCR-1) and den = lambda_pr(inv(X(l))) all in poly-form + */ + for (j = count-1; j >=0; j--) { + num1 = 0; + for (i = deg_omega; i >= 0; i--) { + if (omega[i] != A0) + num1 ^= ALPHA_TO[MODNN(omega[i] + i * root[j])]; + } + num2 = ALPHA_TO[MODNN(root[j] * (FCR - 1) + NN)]; + den = 0; + + /* lambda[i+1] for i even is the formal derivative lambda_pr of lambda[i] */ + for (i = MIN(deg_lambda,NROOTS-1) & ~1; i >= 0; i -=2) { + if(lambda[i+1] != A0) + den ^= ALPHA_TO[MODNN(lambda[i+1] + i * root[j])]; + } +#if DEBUG >= 1 + if (den == 0) { + fprintf(stderr, "\n ERROR: denominator = 0\n"); + count = -1; + goto finish; + } +#endif + /* Apply error to data */ + if (num1 != 0 && loc[j] >= PAD) { + data[loc[j]-PAD] ^= ALPHA_TO[MODNN(INDEX_OF[num1] + INDEX_OF[num2] + NN - INDEX_OF[den])]; + } + } + finish: + if(eras_pos != NULL){ + for(i=0;i -#endif - -#include -#include - -#include "char.h" -#include "rs-common.h" - -int decode_rs_dab(void *p, data_t *data, int *eras_pos, int no_eras){ - int retval; - struct rs *rs = (struct rs *)p; - -#include "decode_rs.h" - - return retval; -} +/* General purpose Reed-Solomon decoder for 8-bit symbols or less + * Copyright 2003 Phil Karn, KA9Q + * May be used under the terms of the GNU Lesser General Public License (LGPL) + */ + +#ifdef DEBUG +#include +#endif + +#include +#include + +#include "char.h" +#include "rs-common.h" + +int decode_rs_dab(void *p, data_t *data, int *eras_pos, int no_eras){ + int retval; + struct rs *rs = (struct rs *)p; + +#include "decode_rs.h" + + return retval; +} diff --git a/src/dabdsp/decoder_adapter.cpp b/src/dabdsp/decoder_adapter.cpp index c7e90db..a06046d 100644 --- a/src/dabdsp/decoder_adapter.cpp +++ b/src/dabdsp/decoder_adapter.cpp @@ -1,175 +1,175 @@ -/* - * Copyright (C) 2018 - * Albrecht Lohofener (albrechtloh@gmx.de) - * - * This file is part of the welle.io. - * Many of the ideas as implemented in welle.io are derived from - * other work, made available through the GNU general Public License. - * All copyrights of the original authors are recognized. - * - * welle.io is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * welle.io 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with welle.io; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - -#include -#include -#include "decoder_adapter.h" - -DecoderAdapter::DecoderAdapter(ProgrammeHandlerInterface &mr, int16_t bitRate, AudioServiceComponentType &dabModus, const std::string &dumpFileName): - bitRate(bitRate), - myInterface(mr), - padDecoder(this, true) -{ - if (dabModus == AudioServiceComponentType::DAB) - decoder = std::make_unique(this, false); - else if (dabModus == AudioServiceComponentType::DABPlus) - decoder = std::make_unique(this, true, false); - else - throw std::runtime_error("DecoderAdapter: Unknown service component"); - - // Open a dump file (XPADxpert) if the user defined it - if (!dumpFileName.empty()) { - FILE *fd = fopen(dumpFileName.c_str(), "wb"); - // w for write, b for binary - if (fd != nullptr) { - dumpFile.reset(fd); - } - } - - // MOT, start of X-PAD data group, see EN 301 234 - padDecoder.SetMOTAppType(12); -} - -void DecoderAdapter::addtoFrame(uint8_t *v) -{ - const size_t length = 24 * bitRate / 8; - std::vector data(length); - - // Convert 8 bits (stored in one uint8) into one uint8 - for (size_t i = 0; i < length; i ++) { - data[i] = 0; - for (int j = 0; j < 8; j ++) { - data[i] <<= 1; - data[i] |= v[8 * i + j] & 01; - } - } - - decoder->Feed(data.data(), length); - - if (dumpFile) { - fwrite(data.data(), length, 1, dumpFile.get()); - } - - myInterface.onFrameErrors(frameErrorCounter); - frameErrorCounter = 0; -} - -void DecoderAdapter::FormatChange(const AUDIO_SERVICE_FORMAT& format) -{ - audioFormat = format.GetSummary(); -} - -void DecoderAdapter::StartAudio(int samplerate, int channels, bool float32) -{ - if (float32 == true) - throw std::runtime_error("DecoderAdapter: Float32 audio samples are not supported"); - - audioSamplerate = samplerate; - audioChannels = channels; -} - -void DecoderAdapter::PutAudio(const uint8_t *data, size_t len) -{ - // Then len is given in bytes. For stereo it is the double times of mono. - // But we need two channels even if we have mono. - // Mono: len = len / 2 * 2 We have len to divide by 2 and for two channels we have multiply by two - // Stereo: len = len / 2 We just need to divide by 2 because it is stereo - size_t bufferSize = audioChannels == 2 ? len/2 : len; - std::vector audio(bufferSize); - - // Convert two uint8 into a int16 sample - for(size_t i=0; i +#include +#include "decoder_adapter.h" + +DecoderAdapter::DecoderAdapter(ProgrammeHandlerInterface &mr, int16_t bitRate, AudioServiceComponentType &dabModus, const std::string &dumpFileName): + bitRate(bitRate), + myInterface(mr), + padDecoder(this, true) +{ + if (dabModus == AudioServiceComponentType::DAB) + decoder = std::make_unique(this, false); + else if (dabModus == AudioServiceComponentType::DABPlus) + decoder = std::make_unique(this, true, false); + else + throw std::runtime_error("DecoderAdapter: Unknown service component"); + + // Open a dump file (XPADxpert) if the user defined it + if (!dumpFileName.empty()) { + FILE *fd = fopen(dumpFileName.c_str(), "wb"); + // w for write, b for binary + if (fd != nullptr) { + dumpFile.reset(fd); + } + } + + // MOT, start of X-PAD data group, see EN 301 234 + padDecoder.SetMOTAppType(12); +} + +void DecoderAdapter::addtoFrame(uint8_t *v) +{ + const size_t length = 24 * bitRate / 8; + std::vector data(length); + + // Convert 8 bits (stored in one uint8) into one uint8 + for (size_t i = 0; i < length; i ++) { + data[i] = 0; + for (int j = 0; j < 8; j ++) { + data[i] <<= 1; + data[i] |= v[8 * i + j] & 01; + } + } + + decoder->Feed(data.data(), length); + + if (dumpFile) { + fwrite(data.data(), length, 1, dumpFile.get()); + } + + myInterface.onFrameErrors(frameErrorCounter); + frameErrorCounter = 0; +} + +void DecoderAdapter::FormatChange(const AUDIO_SERVICE_FORMAT& format) +{ + audioFormat = format.GetSummary(); +} + +void DecoderAdapter::StartAudio(int samplerate, int channels, bool float32) +{ + if (float32 == true) + throw std::runtime_error("DecoderAdapter: Float32 audio samples are not supported"); + + audioSamplerate = samplerate; + audioChannels = channels; +} + +void DecoderAdapter::PutAudio(const uint8_t *data, size_t len) +{ + // Then len is given in bytes. For stereo it is the double times of mono. + // But we need two channels even if we have mono. + // Mono: len = len / 2 * 2 We have len to divide by 2 and for two channels we have multiply by two + // Stereo: len = len / 2 We just need to divide by 2 because it is stereo + size_t bufferSize = audioChannels == 2 ? len/2 : len; + std::vector audio(bufferSize); + + // Convert two uint8 into a int16 sample + for(size_t i=0; i -#include -#include -#include -#include -#include "dab-processor.h" -#include "pad_decoder.h" -#include "radio-controller.h" -#include "subchannel_sink.h" -#include "dab_decoder.h" -#include "dabplus_decoder.h" - -class DecoderAdapter: public DabProcessor, public SubchannelSinkObserver, public PADDecoderObserver -{ - public: - DecoderAdapter(ProgrammeHandlerInterface& mr, - int16_t bitRate, - AudioServiceComponentType &dabModus, - const std::string& dumpFileName); - - virtual void addtoFrame(uint8_t *v); - - // SubchannelSinkObserver impl - virtual void FormatChange(const AUDIO_SERVICE_FORMAT& /*format*/); - virtual void StartAudio(int /*samplerate*/, int /*channels*/, bool /*float32*/); - virtual void PutAudio(const uint8_t* /*data*/, size_t /*len*/); - virtual void ProcessPAD(const uint8_t* /*xpad_data*/, size_t /*xpad_len*/, bool /*exact_xpad_len*/, const uint8_t* /*fpad_data*/); - virtual void AudioError(const std::string& /*hint*/); - virtual void ACCFrameError(const unsigned char /* error*/); - virtual void FECInfo(int /*total_corr_count*/, bool /*uncorr_errors*/); - - // PADDecoderObserver impl - virtual void PADChangeDynamicLabel(const DL_STATE& dl); - virtual void PADChangeSlide(const MOT_FILE& slide); - virtual void PADLengthError(size_t announced_xpad_len, size_t xpad_len); - - private: - int16_t bitRate; - int frameErrorCounter = 0; - ProgrammeHandlerInterface& myInterface; - std::unique_ptr decoder; - PADDecoder padDecoder; - - struct FILEDeleter{ void operator()(FILE* fd){ if (fd) fclose(fd); }}; - std::unique_ptr dumpFile; - - int audioSamplerate = 0; - int audioChannels = 0; - std::string audioFormat; -}; -#endif // DECODER_ADAPTER_H - +/* + * Copyright (C) 2018 + * Albrecht Lohofener (albrechtloh@gmx.de) + * + * This file is part of the welle.io. + * Many of the ideas as implemented in welle.io are derived from + * other work, made available through the GNU general Public License. + * All copyrights of the original authors are recognized. + * + * welle.io is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * welle.io 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with welle.io; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef DECODER_ADAPTER_H +#define DECODER_ADAPTER_H + +#include +#include +#include +#include +#include +#include "dab-processor.h" +#include "pad_decoder.h" +#include "radio-controller.h" +#include "subchannel_sink.h" +#include "dab_decoder.h" +#include "dabplus_decoder.h" + +class DecoderAdapter: public DabProcessor, public SubchannelSinkObserver, public PADDecoderObserver +{ + public: + DecoderAdapter(ProgrammeHandlerInterface& mr, + int16_t bitRate, + AudioServiceComponentType &dabModus, + const std::string& dumpFileName); + + virtual void addtoFrame(uint8_t *v); + + // SubchannelSinkObserver impl + virtual void FormatChange(const AUDIO_SERVICE_FORMAT& /*format*/); + virtual void StartAudio(int /*samplerate*/, int /*channels*/, bool /*float32*/); + virtual void PutAudio(const uint8_t* /*data*/, size_t /*len*/); + virtual void ProcessPAD(const uint8_t* /*xpad_data*/, size_t /*xpad_len*/, bool /*exact_xpad_len*/, const uint8_t* /*fpad_data*/); + virtual void AudioError(const std::string& /*hint*/); + virtual void ACCFrameError(const unsigned char /* error*/); + virtual void FECInfo(int /*total_corr_count*/, bool /*uncorr_errors*/); + + // PADDecoderObserver impl + virtual void PADChangeDynamicLabel(const DL_STATE& dl); + virtual void PADChangeSlide(const MOT_FILE& slide); + virtual void PADLengthError(size_t announced_xpad_len, size_t xpad_len); + + private: + int16_t bitRate; + int frameErrorCounter = 0; + ProgrammeHandlerInterface& myInterface; + std::unique_ptr decoder; + PADDecoder padDecoder; + + struct FILEDeleter{ void operator()(FILE* fd){ if (fd) fclose(fd); }}; + std::unique_ptr dumpFile; + + int audioSamplerate = 0; + int audioChannels = 0; + std::string audioFormat; +}; +#endif // DECODER_ADAPTER_H + diff --git a/src/dabdsp/eep-protection.cpp b/src/dabdsp/eep-protection.cpp index 1219d57..a8d68d3 100644 --- a/src/dabdsp/eep-protection.cpp +++ b/src/dabdsp/eep-protection.cpp @@ -1,153 +1,153 @@ -/* - * Copyright (C) 2013 - * Jan van Katwijk (J.vanKatwijk@gmail.com) - * Lazy Chair Programming - * - * This file is part of the SDR-J (JSDR). - * SDR-J is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * SDR-J 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with SDR-J; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - * The eep handling - */ -#include "dab-constants.h" -#include "eep-protection.h" -#include "protTables.h" - -/** - * \brief eep_deconvolve - * equal error protection, bitRate and protLevel - * define the puncturing table - */ -EEPProtection::EEPProtection(int16_t bitRate, bool profile_is_eep_a, int level) : - Viterbi(24 * bitRate), - outSize(24 * bitRate), - viterbiBlock(outSize * 4 + 24) -{ - if (profile_is_eep_a) { - switch (level) { - case 1: - L1 = 6 * bitRate / 8 - 3; - L2 = 3; - PI1 = getPCodes(24 - 1); - PI2 = getPCodes(23 - 1); - break; - - case 2: - if (bitRate == 8) { - L1 = 5; - L2 = 1; - PI1 = getPCodes(13 - 1); - PI2 = getPCodes(12 - 1); - } - else { - L1 = 2 * bitRate / 8 - 3; - L2 = 4 * bitRate / 8 + 3; - PI1 = getPCodes(14 - 1); - PI2 = getPCodes(13 - 1); - } - break; - - case 3: - L1 = 6 * bitRate / 8 - 3; - L2 = 3; - PI1 = getPCodes(8 - 1); - PI2 = getPCodes(7 - 1); - break; - - case 4: - L1 = 4 * bitRate / 8 - 3; - L2 = 2 * bitRate / 8 + 3; - PI1 = getPCodes(3 - 1); - PI2 = getPCodes(2 - 1); - break; - - default: - throw std::logic_error("Invalid EEP_A level"); - } - } - else { - switch (level) { - case 4: - L1 = 24 * bitRate / 32 - 3; - L2 = 3; - PI1 = getPCodes(2 - 1); - PI2 = getPCodes(1 - 1); - break; - - case 3: - L1 = 24 * bitRate / 32 - 3; - L2 = 3; - PI1 = getPCodes(4 - 1); - PI2 = getPCodes(3 - 1); - break; - - case 2: - L1 = 24 * bitRate / 32 - 3; - L2 = 3; - PI1 = getPCodes(6 - 1); - PI2 = getPCodes(5 - 1); - break; - - case 1: - L1 = 24 * bitRate / 32 - 3; - L2 = 3; - PI1 = getPCodes(10 - 1); - PI2 = getPCodes(9 - 1); - break; - - default: - throw std::logic_error("Invalid EEP_A level"); - } - } -} - -bool EEPProtection::deconvolve(const softbit_t *v, int32_t size, uint8_t *outBuffer) -{ - int16_t i, j; - int32_t inputCounter = 0; - int32_t viterbiCounter = 0; - (void)size; // currently unused - memset(viterbiBlock.data(), 0, (outSize * 4 + 24) * sizeof(softbit_t)); - // - // according to the standard we process the logical frame - // with a pair of tuples - // (L1, PI1), (L2, PI2) - // - for (i = 0; i < L1; i ++) { - for (j = 0; j < 128; j ++) { - if (PI1 [j % 32] != 0) - viterbiBlock[viterbiCounter] = v [inputCounter ++]; - viterbiCounter++; - } - } - - for (i = 0; i < L2; i ++) { - for (j = 0; j < 128; j ++) { - if (PI2 [j % 32] != 0) - viterbiBlock[viterbiCounter] = v [inputCounter ++]; - viterbiCounter++; - } - } - // we had a final block of 24 bits with puncturing according to PI_X - // This block constitutes the 6 * 4 bits of the register itself. - for (i = 0; i < 24; i ++) { - if (PI_X [i] != 0) - viterbiBlock[viterbiCounter] = v [inputCounter ++]; - viterbiCounter++; - } - - Viterbi::deconvolve(viterbiBlock.data(), outBuffer); - return true; -} - +/* + * Copyright (C) 2013 + * Jan van Katwijk (J.vanKatwijk@gmail.com) + * Lazy Chair Programming + * + * This file is part of the SDR-J (JSDR). + * SDR-J is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * SDR-J 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with SDR-J; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * The eep handling + */ +#include "dab-constants.h" +#include "eep-protection.h" +#include "protTables.h" + +/** + * \brief eep_deconvolve + * equal error protection, bitRate and protLevel + * define the puncturing table + */ +EEPProtection::EEPProtection(int16_t bitRate, bool profile_is_eep_a, int level) : + Viterbi(24 * bitRate), + outSize(24 * bitRate), + viterbiBlock(outSize * 4 + 24) +{ + if (profile_is_eep_a) { + switch (level) { + case 1: + L1 = 6 * bitRate / 8 - 3; + L2 = 3; + PI1 = getPCodes(24 - 1); + PI2 = getPCodes(23 - 1); + break; + + case 2: + if (bitRate == 8) { + L1 = 5; + L2 = 1; + PI1 = getPCodes(13 - 1); + PI2 = getPCodes(12 - 1); + } + else { + L1 = 2 * bitRate / 8 - 3; + L2 = 4 * bitRate / 8 + 3; + PI1 = getPCodes(14 - 1); + PI2 = getPCodes(13 - 1); + } + break; + + case 3: + L1 = 6 * bitRate / 8 - 3; + L2 = 3; + PI1 = getPCodes(8 - 1); + PI2 = getPCodes(7 - 1); + break; + + case 4: + L1 = 4 * bitRate / 8 - 3; + L2 = 2 * bitRate / 8 + 3; + PI1 = getPCodes(3 - 1); + PI2 = getPCodes(2 - 1); + break; + + default: + throw std::logic_error("Invalid EEP_A level"); + } + } + else { + switch (level) { + case 4: + L1 = 24 * bitRate / 32 - 3; + L2 = 3; + PI1 = getPCodes(2 - 1); + PI2 = getPCodes(1 - 1); + break; + + case 3: + L1 = 24 * bitRate / 32 - 3; + L2 = 3; + PI1 = getPCodes(4 - 1); + PI2 = getPCodes(3 - 1); + break; + + case 2: + L1 = 24 * bitRate / 32 - 3; + L2 = 3; + PI1 = getPCodes(6 - 1); + PI2 = getPCodes(5 - 1); + break; + + case 1: + L1 = 24 * bitRate / 32 - 3; + L2 = 3; + PI1 = getPCodes(10 - 1); + PI2 = getPCodes(9 - 1); + break; + + default: + throw std::logic_error("Invalid EEP_A level"); + } + } +} + +bool EEPProtection::deconvolve(const softbit_t *v, int32_t size, uint8_t *outBuffer) +{ + int16_t i, j; + int32_t inputCounter = 0; + int32_t viterbiCounter = 0; + (void)size; // currently unused + memset(viterbiBlock.data(), 0, (outSize * 4 + 24) * sizeof(softbit_t)); + // + // according to the standard we process the logical frame + // with a pair of tuples + // (L1, PI1), (L2, PI2) + // + for (i = 0; i < L1; i ++) { + for (j = 0; j < 128; j ++) { + if (PI1 [j % 32] != 0) + viterbiBlock[viterbiCounter] = v [inputCounter ++]; + viterbiCounter++; + } + } + + for (i = 0; i < L2; i ++) { + for (j = 0; j < 128; j ++) { + if (PI2 [j % 32] != 0) + viterbiBlock[viterbiCounter] = v [inputCounter ++]; + viterbiCounter++; + } + } + // we had a final block of 24 bits with puncturing according to PI_X + // This block constitutes the 6 * 4 bits of the register itself. + for (i = 0; i < 24; i ++) { + if (PI_X [i] != 0) + viterbiBlock[viterbiCounter] = v [inputCounter ++]; + viterbiCounter++; + } + + Viterbi::deconvolve(viterbiBlock.data(), outBuffer); + return true; +} + diff --git a/src/dabdsp/eep-protection.h b/src/dabdsp/eep-protection.h index 32fa865..0ddd524 100644 --- a/src/dabdsp/eep-protection.h +++ b/src/dabdsp/eep-protection.h @@ -1,45 +1,45 @@ -/* - * Copyright (C) 2013 - * Jan van Katwijk (J.vanKatwijk@gmail.com) - * Lazy Chair Programming - * - * This file is part of the SDR-J (JSDR). - * SDR-J is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * SDR-J 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with SDR-J; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ -#ifndef EEP_PROTECTION -#define EEP_PROTECTION - -#include -#include -#include -#include "protection.h" -#include "viterbi.h" - -class EEPProtection: public Protection, public Viterbi { - public: - EEPProtection(int16_t bitRate, bool profile_is_eep_a, int level); - bool deconvolve(const softbit_t *v, int32_t size, uint8_t *outBuffer); - private: - int16_t L1; - int16_t L2; - const int8_t *PI1; - const int8_t *PI2; - int32_t outSize; - std::vector viterbiBlock; -}; - -#endif - +/* + * Copyright (C) 2013 + * Jan van Katwijk (J.vanKatwijk@gmail.com) + * Lazy Chair Programming + * + * This file is part of the SDR-J (JSDR). + * SDR-J is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * SDR-J 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with SDR-J; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef EEP_PROTECTION +#define EEP_PROTECTION + +#include +#include +#include +#include "protection.h" +#include "viterbi.h" + +class EEPProtection: public Protection, public Viterbi { + public: + EEPProtection(int16_t bitRate, bool profile_is_eep_a, int level); + bool deconvolve(const softbit_t *v, int32_t size, uint8_t *outBuffer); + private: + int16_t L1; + int16_t L2; + const int8_t *PI1; + const int8_t *PI2; + int32_t outSize; + std::vector viterbiBlock; +}; + +#endif + diff --git a/src/dabdsp/encode_rs.h b/src/dabdsp/encode_rs.h index 3cb5294..2c157f9 100644 --- a/src/dabdsp/encode_rs.h +++ b/src/dabdsp/encode_rs.h @@ -1,58 +1,58 @@ -/* The guts of the Reed-Solomon encoder, meant to be #included - * into a function body with the following typedefs, macros and variables supplied - * according to the code parameters: - - * data_t - a typedef for the data symbol - * data_t data[] - array of NN-NROOTS-PAD and type data_t to be encoded - * data_t parity[] - an array of NROOTS and type data_t to be written with parity symbols - * NROOTS - the number of roots in the RS code generator polynomial, - * which is the same as the number of parity symbols in a block. - Integer variable or literal. - * - * NN - the total number of symbols in a RS block. Integer variable or literal. - * PAD - the number of pad symbols in a block. Integer variable or literal. - * ALPHA_TO - The address of an array of NN elements to convert Galois field - * elements in index (log) form to polynomial form. Read only. - * INDEX_OF - The address of an array of NN elements to convert Galois field - * elements in polynomial form to index (log) form. Read only. - * MODNN - a function to reduce its argument modulo NN. May be inline or a macro. - * GENPOLY - an array of NROOTS+1 elements containing the generator polynomial in index form - - * The memset() and memmove() functions are used. The appropriate header - * file declaring these functions (usually ) must be included by the calling - * program. - - * Copyright 2004, Phil Karn, KA9Q - * May be used under the terms of the GNU Lesser General Public License (LGPL) - */ - - -#undef A0 -#define A0 (NN) /* Special reserved value encoding zero in index form */ - -{ - int i, j; - data_t feedback; - - memset(parity,0,NROOTS*sizeof(data_t)); - - for(i=0;i) must be included by the calling + * program. + + * Copyright 2004, Phil Karn, KA9Q + * May be used under the terms of the GNU Lesser General Public License (LGPL) + */ + + +#undef A0 +#define A0 (NN) /* Special reserved value encoding zero in index form */ + +{ + int i, j; + data_t feedback; + + memset(parity,0,NROOTS*sizeof(data_t)); + + for(i=0;i - -#include "char.h" -#include "rs-common.h" - -void encode_rs_dab(void *p,data_t *data, data_t *parity){ - struct rs *rs = (struct rs *)p; - -#include "encode_rs.h" - -} +/* Reed-Solomon encoder + * Copyright 2002, Phil Karn, KA9Q + * May be used under the terms of the GNU Lesser General Public License (LGPL) + */ +#include + +#include "char.h" +#include "rs-common.h" + +void encode_rs_dab(void *p,data_t *data, data_t *parity){ + struct rs *rs = (struct rs *)p; + +#include "encode_rs.h" + +} diff --git a/src/dabdsp/energy_dispersal.h b/src/dabdsp/energy_dispersal.h index ff484d7..73f5cb7 100644 --- a/src/dabdsp/energy_dispersal.h +++ b/src/dabdsp/energy_dispersal.h @@ -1,60 +1,60 @@ -/* - * Copyright (C) 2018 - * Matthias P. Braendli (matthias.braendli@mpb.li) - * - * Copyright (C) 2013 - * Jan van Katwijk (J.vanKatwijk@gmail.com) - * Lazy Chair Programming - * - * This file is part of the SDR-J (JSDR). - * SDR-J is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * SDR-J 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with SDR-J; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ -#ifndef __ENERGY_DISPERSAL -#define __ENERGY_DISPERSAL - -#include -#include -#include -#include - -class EnergyDispersal { - public: - void dedisperse(std::vector& data) - { - if (dispersalVector.size() != data.size()) { - std::vector shiftRegister(9, 1); - - dispersalVector.resize(data.size()); - - for (size_t i = 0; i < data.size(); i++) { - uint8_t b = shiftRegister[8] ^ shiftRegister[4]; - for (int j = 8; j > 0; j--) - shiftRegister[j] = shiftRegister[j - 1]; - shiftRegister[0] = b; - dispersalVector[i] ^= b; - } - } - - for (size_t i = 0; i < data.size(); i++) { - data[i] ^= dispersalVector[i]; - } - } - - private: - std::vector dispersalVector; -}; - -#endif // __ENERGY_DISPERSAL +/* + * Copyright (C) 2018 + * Matthias P. Braendli (matthias.braendli@mpb.li) + * + * Copyright (C) 2013 + * Jan van Katwijk (J.vanKatwijk@gmail.com) + * Lazy Chair Programming + * + * This file is part of the SDR-J (JSDR). + * SDR-J is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * SDR-J 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with SDR-J; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __ENERGY_DISPERSAL +#define __ENERGY_DISPERSAL + +#include +#include +#include +#include + +class EnergyDispersal { + public: + void dedisperse(std::vector& data) + { + if (dispersalVector.size() != data.size()) { + std::vector shiftRegister(9, 1); + + dispersalVector.resize(data.size()); + + for (size_t i = 0; i < data.size(); i++) { + uint8_t b = shiftRegister[8] ^ shiftRegister[4]; + for (int j = 8; j > 0; j--) + shiftRegister[j] = shiftRegister[j - 1]; + shiftRegister[0] = b; + dispersalVector[i] ^= b; + } + } + + for (size_t i = 0; i < data.size(); i++) { + data[i] ^= dispersalVector[i]; + } + } + + private: + std::vector dispersalVector; +}; + +#endif // __ENERGY_DISPERSAL diff --git a/src/dabdsp/fec.h b/src/dabdsp/fec.h index eaa64a0..54e9fb3 100644 --- a/src/dabdsp/fec.h +++ b/src/dabdsp/fec.h @@ -1,30 +1,30 @@ -/* Main header for reduced libfec. - * - * The FEC code in this folder is - * Copyright 2003 Phil Karn, KA9Q - * May be used under the terms of the GNU Lesser General Public License (LGPL) - */ - -#pragma once - -#include - -#include "char.h" -#include "rs-common.h" - -/* Initialize a Reed-Solomon codec - * symsize = symbol size, bits - * gfpoly = Field generator polynomial coefficients - * fcr = first root of RS code generator polynomial, index form - * prim = primitive element to generate polynomial roots - * nroots = RS code generator polynomial degree (number of roots) - * pad = padding bytes at front of shortened block - */ -void *init_rs_dab(int symsize,int gfpoly,int fcr,int prim,int nroots,int pad); - -int decode_rs_dab(void *p, data_t *data, int *eras_pos, int no_eras); - -void encode_rs_dab(void *p,data_t *data, data_t *parity); - -void free_rs_dab(void *p); - +/* Main header for reduced libfec. + * + * The FEC code in this folder is + * Copyright 2003 Phil Karn, KA9Q + * May be used under the terms of the GNU Lesser General Public License (LGPL) + */ + +#pragma once + +#include + +#include "char.h" +#include "rs-common.h" + +/* Initialize a Reed-Solomon codec + * symsize = symbol size, bits + * gfpoly = Field generator polynomial coefficients + * fcr = first root of RS code generator polynomial, index form + * prim = primitive element to generate polynomial roots + * nroots = RS code generator polynomial degree (number of roots) + * pad = padding bytes at front of shortened block + */ +void *init_rs_dab(int symsize,int gfpoly,int fcr,int prim,int nroots,int pad); + +int decode_rs_dab(void *p, data_t *data, int *eras_pos, int no_eras); + +void encode_rs_dab(void *p,data_t *data, data_t *parity); + +void free_rs_dab(void *p); + diff --git a/src/dabdsp/fft.cpp b/src/dabdsp/fft.cpp index fa9dd87..c2bd0dc 100644 --- a/src/dabdsp/fft.cpp +++ b/src/dabdsp/fft.cpp @@ -1,168 +1,168 @@ -/* - * Copyright (C) 2018 - * Matthias P. Braendli (matthias.braendli@mpb.li) - * - * Copyright (C) 2008, 2009, 2010 - * Jan van Katwijk (J.vanKatwijk@gmail.com) - * Lazy Chair Computing - * - * This file is part of the SDR-J. - * Many of the ideas as implemented in SDR-J are derived from - * other work, made available through the GNU general Public License. - * All copyrights of the original authors are recognized. - * - * SDR-J is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * SDR-J 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with SDR-J; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ -#include "fft.h" -#include - -namespace fft { - -#ifndef KISSFFT -Forward::Forward(int32_t fft_size) -{ - vector = (DSPCOMPLEX *)fftwf_malloc(sizeof (DSPCOMPLEX) * fft_size); - memset((void*)vector, 0, sizeof(DSPCOMPLEX) * fft_size); - plan = FFTW_PLAN_DFT_1D(fft_size, - reinterpret_cast(vector), - reinterpret_cast(vector), - FFTW_FORWARD, FFTW_ESTIMATE); -} - -Forward::~Forward() -{ - FFTW_DESTROY_PLAN(plan); - FFTW_FREE(vector); -} - -DSPCOMPLEX* Forward::getVector() -{ - return vector; -} - -void Forward::do_FFT() -{ - FFTW_EXECUTE (plan); -} - -Backward::Backward(int32_t fft_size) : - fft_size(fft_size) -{ - vector = (DSPCOMPLEX*)fftwf_malloc(sizeof(DSPCOMPLEX) * fft_size); - for (int i = 0; i < fft_size; i ++) { - vector [i] = 0; - } - plan = FFTW_PLAN_DFT_1D(fft_size, - reinterpret_cast(vector), - reinterpret_cast(vector), - FFTW_BACKWARD, FFTW_ESTIMATE); -} - -Backward::~Backward () -{ - FFTW_DESTROY_PLAN(plan); - FFTW_FREE(vector); -} - -DSPCOMPLEX* Backward::getVector() -{ - return vector; -} - -void Backward::do_IFFT() -{ - FFTW_EXECUTE(plan); - - const DSPFLOAT factor = 1.0 / DSPFLOAT(fft_size); - - // scale all entries - for (int i = 0; i < fft_size; i++) { - vector[i] *= factor; - } -} - -#else // Kiss FFT - -Forward::Forward(int32_t fft_size) : - fft_size(fft_size) -{ - cfg = kiss_fft_alloc(fft_size, 0, NULL, NULL); - - fin = (DSPCOMPLEX*)malloc(fft_size * sizeof(DSPCOMPLEX)); - fout = (DSPCOMPLEX*)malloc(fft_size * sizeof(DSPCOMPLEX)); - - memset((void*)fin, 0, fft_size * sizeof(DSPCOMPLEX)); - memset((void*)fout, 0, fft_size * sizeof(DSPCOMPLEX)); -} - -Forward::~Forward() -{ - free(cfg); - free(fin); - free(fout); -} - -DSPCOMPLEX* Forward::getVector() -{ - return fin; -} - -void Forward::do_FFT() -{ - kiss_fft(cfg, (kiss_fft_cpx*)fin, (kiss_fft_cpx*)fout); - memcpy(fin, fout, fft_size * sizeof(DSPCOMPLEX)); -} - -Backward::Backward(int32_t fft_size) : - fft_size(fft_size) -{ - cfg = kiss_fft_alloc(fft_size, 1, NULL, NULL); - - fin = (DSPCOMPLEX*)malloc(fft_size * sizeof(DSPCOMPLEX)); - fout = (DSPCOMPLEX*)malloc(fft_size * sizeof(DSPCOMPLEX)); - - memset((void*)fin, 0, fft_size * sizeof(DSPCOMPLEX)); - memset((void*)fout, 0, fft_size * sizeof(DSPCOMPLEX)); -} - -Backward::~Backward() -{ - free(cfg); - free(fin); - free(fout); -} - -DSPCOMPLEX*Backward::getVector() -{ - return fin; -} - -void Backward::do_IFFT() -{ - const DSPFLOAT factor = 1.0f / DSPFLOAT(fft_size); - - kiss_fft(cfg, (kiss_fft_cpx*)fin, (kiss_fft_cpx*)fout); - - // Scale all entries - for (int i = 0; i < fft_size; i ++) { - fout[i] *= factor; - } - - memcpy(fin, fout, fft_size * sizeof(kiss_fft_cpx)); -} - -#endif - -} // namespace fft +/* + * Copyright (C) 2018 + * Matthias P. Braendli (matthias.braendli@mpb.li) + * + * Copyright (C) 2008, 2009, 2010 + * Jan van Katwijk (J.vanKatwijk@gmail.com) + * Lazy Chair Computing + * + * This file is part of the SDR-J. + * Many of the ideas as implemented in SDR-J are derived from + * other work, made available through the GNU general Public License. + * All copyrights of the original authors are recognized. + * + * SDR-J is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * SDR-J 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with SDR-J; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "fft.h" +#include + +namespace fft { + +#ifndef KISSFFT +Forward::Forward(int32_t fft_size) +{ + vector = (DSPCOMPLEX *)fftwf_malloc(sizeof (DSPCOMPLEX) * fft_size); + memset((void*)vector, 0, sizeof(DSPCOMPLEX) * fft_size); + plan = FFTW_PLAN_DFT_1D(fft_size, + reinterpret_cast(vector), + reinterpret_cast(vector), + FFTW_FORWARD, FFTW_ESTIMATE); +} + +Forward::~Forward() +{ + FFTW_DESTROY_PLAN(plan); + FFTW_FREE(vector); +} + +DSPCOMPLEX* Forward::getVector() +{ + return vector; +} + +void Forward::do_FFT() +{ + FFTW_EXECUTE (plan); +} + +Backward::Backward(int32_t fft_size) : + fft_size(fft_size) +{ + vector = (DSPCOMPLEX*)fftwf_malloc(sizeof(DSPCOMPLEX) * fft_size); + for (int i = 0; i < fft_size; i ++) { + vector [i] = 0; + } + plan = FFTW_PLAN_DFT_1D(fft_size, + reinterpret_cast(vector), + reinterpret_cast(vector), + FFTW_BACKWARD, FFTW_ESTIMATE); +} + +Backward::~Backward () +{ + FFTW_DESTROY_PLAN(plan); + FFTW_FREE(vector); +} + +DSPCOMPLEX* Backward::getVector() +{ + return vector; +} + +void Backward::do_IFFT() +{ + FFTW_EXECUTE(plan); + + const DSPFLOAT factor = 1.0 / DSPFLOAT(fft_size); + + // scale all entries + for (int i = 0; i < fft_size; i++) { + vector[i] *= factor; + } +} + +#else // Kiss FFT + +Forward::Forward(int32_t fft_size) : + fft_size(fft_size) +{ + cfg = kiss_fft_alloc(fft_size, 0, NULL, NULL); + + fin = (DSPCOMPLEX*)malloc(fft_size * sizeof(DSPCOMPLEX)); + fout = (DSPCOMPLEX*)malloc(fft_size * sizeof(DSPCOMPLEX)); + + memset((void*)fin, 0, fft_size * sizeof(DSPCOMPLEX)); + memset((void*)fout, 0, fft_size * sizeof(DSPCOMPLEX)); +} + +Forward::~Forward() +{ + free(cfg); + free(fin); + free(fout); +} + +DSPCOMPLEX* Forward::getVector() +{ + return fin; +} + +void Forward::do_FFT() +{ + kiss_fft(cfg, (kiss_fft_cpx*)fin, (kiss_fft_cpx*)fout); + memcpy(fin, fout, fft_size * sizeof(DSPCOMPLEX)); +} + +Backward::Backward(int32_t fft_size) : + fft_size(fft_size) +{ + cfg = kiss_fft_alloc(fft_size, 1, NULL, NULL); + + fin = (DSPCOMPLEX*)malloc(fft_size * sizeof(DSPCOMPLEX)); + fout = (DSPCOMPLEX*)malloc(fft_size * sizeof(DSPCOMPLEX)); + + memset((void*)fin, 0, fft_size * sizeof(DSPCOMPLEX)); + memset((void*)fout, 0, fft_size * sizeof(DSPCOMPLEX)); +} + +Backward::~Backward() +{ + free(cfg); + free(fin); + free(fout); +} + +DSPCOMPLEX*Backward::getVector() +{ + return fin; +} + +void Backward::do_IFFT() +{ + const DSPFLOAT factor = 1.0f / DSPFLOAT(fft_size); + + kiss_fft(cfg, (kiss_fft_cpx*)fin, (kiss_fft_cpx*)fout); + + // Scale all entries + for (int i = 0; i < fft_size; i ++) { + fout[i] *= factor; + } + + memcpy(fin, fout, fft_size * sizeof(kiss_fft_cpx)); +} + +#endif + +} // namespace fft diff --git a/src/dabdsp/fft.h b/src/dabdsp/fft.h index 49be092..5a92f4c 100644 --- a/src/dabdsp/fft.h +++ b/src/dabdsp/fft.h @@ -1,120 +1,120 @@ -/* - * Copyright (C) 2018 - * Matthias P. Braendli (matthias.braendli@mpb.li) - * - * Copyright (C) 2009 .. 2014 - * Jan van Katwijk (J.vanKatwijk@gmail.com) - * Lazy Chair Programming - * - * This file is part of the SDR-J. - * Many of the ideas as implemented in SDR-J are derived from - * other work, made available through the GNU general Public License. - * All copyrights of the original authors are recognized. - * - * SDR-J is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * SDR-J 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ESDR; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - -#ifndef _COMMON_FFT -#define _COMMON_FFT - -// Wrappers around fftwf and KISS FFT for both forward and backward FFTs -#include "dab-constants.h" - -namespace fft { - -#ifndef KISSFFT -//# define FFTW_MALLOC fftwf_malloc -# define FFTW_PLAN_DFT_1D fftwf_plan_dft_1d -# define FFTW_DESTROY_PLAN fftwf_destroy_plan -# define FFTW_FREE fftwf_free -# define FFTW_PLAN fftwf_plan -# define FFTW_EXECUTE fftwf_execute -# include - -class Forward { - public: - Forward(int32_t fft_size); - Forward(const Forward&) = delete; - Forward& operator=(const Forward&) = delete; - ~Forward(void); - DSPCOMPLEX *getVector(void); - void do_FFT(void); - - private: - DSPCOMPLEX *vector; - FFTW_PLAN plan; -}; - -class Backward -{ - public: - Backward(int32_t); - ~Backward(void); - Backward(const Backward&) = delete; - Backward& operator=(const Backward&) = delete; - DSPCOMPLEX *getVector(void); - void do_IFFT(void); - - private: - int32_t fft_size; - DSPCOMPLEX *vector; - FFTW_PLAN plan; -}; - -#else -# include "kiss_fft.h" - -class Forward -{ - public: - Forward(int32_t fft_size); - ~Forward(void); - Forward(const Forward&) = delete; - Forward& operator=(const Forward&) = delete; - DSPCOMPLEX *getVector(void); - void do_FFT(void); - - private: - int32_t fft_size; - - kiss_fft_cfg cfg; - DSPCOMPLEX *fin; - DSPCOMPLEX *fout; -}; - -class Backward -{ - public: - Backward(int32_t fft_size); - ~Backward(void); - Backward(const Backward&) = delete; - Backward& operator=(const Backward&) = delete; - DSPCOMPLEX *getVector(void); - void do_IFFT(void); - - private: - int32_t fft_size; - - kiss_fft_cfg cfg; - DSPCOMPLEX *fin; - DSPCOMPLEX *fout; -}; -#endif - -} // namespace fft - -#endif - +/* + * Copyright (C) 2018 + * Matthias P. Braendli (matthias.braendli@mpb.li) + * + * Copyright (C) 2009 .. 2014 + * Jan van Katwijk (J.vanKatwijk@gmail.com) + * Lazy Chair Programming + * + * This file is part of the SDR-J. + * Many of the ideas as implemented in SDR-J are derived from + * other work, made available through the GNU general Public License. + * All copyrights of the original authors are recognized. + * + * SDR-J is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * SDR-J 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ESDR; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _COMMON_FFT +#define _COMMON_FFT + +// Wrappers around fftwf and KISS FFT for both forward and backward FFTs +#include "dab-constants.h" + +namespace fft { + +#ifndef KISSFFT +//# define FFTW_MALLOC fftwf_malloc +# define FFTW_PLAN_DFT_1D fftwf_plan_dft_1d +# define FFTW_DESTROY_PLAN fftwf_destroy_plan +# define FFTW_FREE fftwf_free +# define FFTW_PLAN fftwf_plan +# define FFTW_EXECUTE fftwf_execute +# include + +class Forward { + public: + Forward(int32_t fft_size); + Forward(const Forward&) = delete; + Forward& operator=(const Forward&) = delete; + ~Forward(void); + DSPCOMPLEX *getVector(void); + void do_FFT(void); + + private: + DSPCOMPLEX *vector; + FFTW_PLAN plan; +}; + +class Backward +{ + public: + Backward(int32_t); + ~Backward(void); + Backward(const Backward&) = delete; + Backward& operator=(const Backward&) = delete; + DSPCOMPLEX *getVector(void); + void do_IFFT(void); + + private: + int32_t fft_size; + DSPCOMPLEX *vector; + FFTW_PLAN plan; +}; + +#else +# include "kiss_fft.h" + +class Forward +{ + public: + Forward(int32_t fft_size); + ~Forward(void); + Forward(const Forward&) = delete; + Forward& operator=(const Forward&) = delete; + DSPCOMPLEX *getVector(void); + void do_FFT(void); + + private: + int32_t fft_size; + + kiss_fft_cfg cfg; + DSPCOMPLEX *fin; + DSPCOMPLEX *fout; +}; + +class Backward +{ + public: + Backward(int32_t fft_size); + ~Backward(void); + Backward(const Backward&) = delete; + Backward& operator=(const Backward&) = delete; + DSPCOMPLEX *getVector(void); + void do_IFFT(void); + + private: + int32_t fft_size; + + kiss_fft_cfg cfg; + DSPCOMPLEX *fin; + DSPCOMPLEX *fout; +}; +#endif + +} // namespace fft + +#endif + diff --git a/src/dabdsp/fib-processor.cpp b/src/dabdsp/fib-processor.cpp index c81d63b..f0e31fe 100644 --- a/src/dabdsp/fib-processor.cpp +++ b/src/dabdsp/fib-processor.cpp @@ -1,1346 +1,1346 @@ -/* - * Copyright (C) 2020 - * Matthias P. Braendli (matthias.braendli@mpb.li) - * - * Copyright (C) 2014 - * Jan van Katwijk (J.vanKatwijk@gmail.com) - * Lazy Chair Programming - * - * This file is part of the SDR-J (JSDR). - * SDR-J is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * SDR-J 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with SDR-J; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - * fib and fig processor - */ -#include -#include -#include -#include -#include - -#include "fib-processor.h" -#include "charsets.h" -#include "MathHelper.h" - -FIBProcessor::FIBProcessor(RadioControllerInterface& mr) : - myRadioInterface(mr) -{ - clearEnsemble(); -} - -// FIB's are segments of 256 bits. When here, we already -// passed the crc and we start unpacking into FIGs -// This is merely a dispatcher -void FIBProcessor::processFIB(uint8_t *p, uint16_t fib) -{ - int8_t processedBytes = 0; - uint8_t *d = p; - - std::lock_guard lock(mutex); - - (void)fib; - while (processedBytes < 30) { - const uint8_t FIGtype = getBits_3 (d, 0); - switch (FIGtype) { - case 0: - process_FIG0(d); - break; - - case 1: - process_FIG1(d); - break; - - case 2: - process_FIG2(d); - break; - - case 7: - return; - - default: - //std::clog << "FIG%d present" << FIGtype << std::endl; - break; - } - // Thanks to Ronny Kunze, who discovered that I used - // a p rather than a d - processedBytes += getBits_5 (d, 3) + 1; - d = p + processedBytes * 8; - } -} -// -// Handle ensemble is all through FIG0 -// -void FIBProcessor::process_FIG0 (uint8_t *d) -{ - uint8_t extension = getBits_5 (d, 8 + 3); - //uint8_t CN = getBits_1 (d, 8 + 0); - - switch (extension) { - case 0: FIG0Extension0 (d); break; - case 1: FIG0Extension1 (d); break; - case 2: FIG0Extension2 (d); break; - case 3: FIG0Extension3 (d); break; - case 5: FIG0Extension5 (d); break; - case 8: FIG0Extension8 (d); break; - case 9: FIG0Extension9 (d); break; - case 10: FIG0Extension10 (d); break; - case 14: FIG0Extension14 (d); break; - case 13: FIG0Extension13 (d); break; - case 17: FIG0Extension17 (d); break; - case 18: FIG0Extension18 (d); break; - case 19: FIG0Extension19 (d); break; - case 21: FIG0Extension21 (d); break; - case 22: FIG0Extension22 (d); break; - default: - // std::clog << "fib-processor:" << "FIG0/%d passed by\n", extension) << std::endl; - break; - } -} - - -// FIG0/0 indicated a change in channel organization -// we are not equipped for that, so we just return -// control to the init -void FIBProcessor::FIG0Extension0 (uint8_t *d) -{ - uint8_t changeflag; - uint16_t highpart, lowpart; - int16_t occurrenceChange; - uint8_t CN = getBits_1 (d, 8 + 0); - (void)CN; - - uint16_t eId = getBits(d, 16, 16); - - if (ensembleId != eId) { - ensembleId = eId; - myRadioInterface.onNewEnsemble(ensembleId); - } - - changeflag = getBits_2 (d, 16 + 16); - - highpart = getBits_5 (d, 16 + 19) % 20; - (void)highpart; - lowpart = getBits_8 (d, 16 + 24) % 250; - (void)lowpart; - occurrenceChange = getBits_8 (d, 16 + 32); - (void)occurrenceChange; - - // In transmission mode I, because four ETI frames make one transmission frame, we will - // see lowpart == 0 only every twelve seconds, and not 6 as expected by the 250 overflow value. - if (lowpart == 0) { - timeLastFCT0Frame = std::chrono::system_clock::now(); - } - - if (changeflag == 0) - return; - // else if (changeflag == 1) { - // std::clog << "fib-processor:" << "Changes in sub channel organization\n") << std::endl; - // std::clog << "fib-processor:" << "cifcount = %d\n", highpart * 250 + lowpart) << std::endl; - // std::clog << "fib-processor:" << "Change happening in %d CIFs\n", occurrenceChange) << std::endl; - // } - // else if (changeflag == 3) { - // std::clog << "fib-processor:" << "Changes in subchannel and service organization\n") << std::endl; - // std::clog << "fib-processor:" << "cifcount = %d\n", highpart * 250 + lowpart) << std::endl; - // std::clog << "fib-processor:" << "Change happening in %d CIFs\n", occurrenceChange) << std::endl; - // } - // std::clog << "fib-processor: " << "changes in config not supported, choose again" << std::endl; -} - -// FIG0 extension 1 creates a mapping between the -// sub channel identifications and the positions in the -// relevant CIF. -void FIBProcessor::FIG0Extension1 (uint8_t *d) -{ - int16_t used = 2; // offset in bytes - int16_t Length = getBits_5 (d, 3); - uint8_t PD_bit = getBits_1 (d, 8 + 2); - //uint8_t CN = getBits_1 (d, 8 + 0); - - while (used < Length - 1) - used = HandleFIG0Extension1 (d, used, PD_bit); -} - -// defining the channels -int16_t FIBProcessor::HandleFIG0Extension1( - uint8_t *d, - int16_t offset, - uint8_t pd) -{ - int16_t bitOffset = offset * 8; - const int16_t subChId = getBits_6 (d, bitOffset); - const int16_t startAdr = getBits(d, bitOffset + 6, 10); - subChannels[subChId].programmeNotData = pd; - subChannels[subChId].subChId = subChId; - subChannels[subChId].startAddr = startAdr; - if (getBits_1 (d, bitOffset + 16) == 0) { // UEP, short form - int16_t tableIx = getBits_6 (d, bitOffset + 18); - auto& ps = subChannels[subChId].protectionSettings; - ps.uepTableIndex = tableIx; - ps.shortForm = true; - ps.uepLevel = ProtLevel[tableIx][1]; - - subChannels[subChId].length = ProtLevel[tableIx][0]; - bitOffset += 24; - } - else { // EEP, long form - auto& ps = subChannels[subChId].protectionSettings; - ps.shortForm = false; - int16_t option = getBits_3(d, bitOffset + 17); - if (option == 0) { - ps.eepProfile = EEPProtectionProfile::EEP_A; - } - else if (option == 1) { - ps.eepProfile = EEPProtectionProfile::EEP_B; - } - - if (option == 0 or // EEP-A protection - option == 1) { // EEP-B protection - int16_t protLevel = getBits_2(d, bitOffset + 20); - switch (protLevel) { - case 0: - ps.eepLevel = EEPProtectionLevel::EEP_1; - break; - case 1: - ps.eepLevel = EEPProtectionLevel::EEP_2; - break; - case 2: - ps.eepLevel = EEPProtectionLevel::EEP_3; - break; - case 3: - ps.eepLevel = EEPProtectionLevel::EEP_4; - break; - default: - //std::clog << "Warning, FIG0/1 for " << subChId << - // " has invalid EEP protection level " << protLevel << - // std::endl; - break; - } - - int16_t subChanSize = getBits(d, bitOffset + 22, 10); - subChannels[subChId].length = subChanSize; - } - else { - //std::clog << "Warning, FIG0/1 for " << subChId << - // " has invalid protection option " << option << std::endl; - } - - bitOffset += 32; - } - - return bitOffset / 8; // we return bytes -} - -void FIBProcessor::FIG0Extension2 (uint8_t *d) -{ - int16_t used = 2; // offset in bytes - int16_t Length = getBits_5 (d, 3); - uint8_t PD_bit = getBits_1 (d, 8 + 2); - uint8_t CN = getBits_1 (d, 8 + 0); - - while (used < Length) { - used = HandleFIG0Extension2(d, used, CN, PD_bit); - } -} - -// Note Offset is in bytes -// With FIG0/2 we bind the channels to Service Ids -int16_t FIBProcessor::HandleFIG0Extension2( - uint8_t *d, - int16_t offset, - uint8_t cn, - uint8_t pd) -{ - (void)cn; - int16_t lOffset = 8 * offset; - int16_t i; - uint8_t ecc; - uint8_t cId; - uint32_t SId; - int16_t numberofComponents; - - if (pd == 1) { // long Sid - ecc = getBits_8(d, lOffset); (void)ecc; - cId = getBits_4(d, lOffset + 1); - SId = getBits(d, lOffset, 32); - lOffset += 32; - } - else { - cId = getBits_4(d, lOffset); (void)cId; - SId = getBits(d, lOffset + 4, 12); - SId = getBits(d, lOffset, 16); - lOffset += 16; - } - - // Keep track how often we see a service using a saturating counter. - // Every time a service is signalled, we increment the counter. - // If the counter is >= 2, we consider the service. Every second, we - // decrement all counters by one. - // This avoids that misdecoded services appear and stay in the list. - using namespace std::chrono; - const auto now = steady_clock::now(); - if (timeLastServiceDecrement + seconds(1) < now) { - - auto it = serviceRepeatCount.begin(); - while (it != serviceRepeatCount.end()) { - if (it->second > 0) { - it->second--; - ++it; - } - else if (it->second == 0) { - dropService(it->second); - it = serviceRepeatCount.erase(it); - } - else { - ++it; - } - } - - timeLastServiceDecrement = now; - -#if 0 - std::stringstream ss; - ss << "Counters: "; - for (auto& c : serviceRepeatCount) { - ss << " " << c.first << ":" << (int)c.second; - } - std::cerr << ss.str() << std::endl; -#endif - } - - if (serviceRepeatCount[SId] < 4) { - serviceRepeatCount[SId]++; - } - - if (findServiceId(SId) == nullptr and serviceRepeatCount[SId] >= 2) { - services.emplace_back(SId); - myRadioInterface.onServiceDetected(SId); - } - - numberofComponents = getBits_4(d, lOffset + 4); - lOffset += 8; - - for (i = 0; i < numberofComponents; i ++) { - uint8_t TMid = getBits_2 (d, lOffset); - if (TMid == 00) { // Audio - uint8_t ASCTy = getBits_6 (d, lOffset + 2); - uint8_t SubChId = getBits_6 (d, lOffset + 8); - uint8_t PS_flag = getBits_1 (d, lOffset + 14); - bindAudioService(TMid, SId, i, SubChId, PS_flag, ASCTy); - } - else if (TMid == 1) { // MSC stream data - uint8_t DSCTy = getBits_6 (d, lOffset + 2); - uint8_t SubChId = getBits_6 (d, lOffset + 8); - uint8_t PS_flag = getBits_1 (d, lOffset + 14); - bindDataStreamService(TMid, SId, i, SubChId, PS_flag, DSCTy); - } - else if (TMid == 3) { // MSC packet data - int16_t SCId = getBits (d, lOffset + 2, 12); - uint8_t PS_flag = getBits_1 (d, lOffset + 14); - uint8_t CA_flag = getBits_1 (d, lOffset + 15); - bindPacketService(TMid, SId, i, SCId, PS_flag, CA_flag); - } - else { - // reserved - } - lOffset += 16; - } - return lOffset / 8; // in Bytes -} - -// The Extension 3 of FIG type 0 (FIG 0/3) gives -// additional information about the service component -// description in packet mode. -// manual: page 55 -void FIBProcessor::FIG0Extension3 (uint8_t *d) -{ - int16_t used = 2; - int16_t Length = getBits_5 (d, 3); - - while (used < Length) { - used = HandleFIG0Extension3 (d, used); - } -} - -// DSCTy DataService Component Type -int16_t FIBProcessor::HandleFIG0Extension3(uint8_t *d, int16_t used) -{ - int16_t SCId = getBits (d, used * 8, 12); - //int16_t CAOrgflag = getBits_1 (d, used * 8 + 15); - int16_t DGflag = getBits_1 (d, used * 8 + 16); - int16_t DSCTy = getBits_6 (d, used * 8 + 18); - int16_t SubChId = getBits_6 (d, used * 8 + 24); - int16_t packetAddress = getBits (d, used * 8 + 30, 10); - //uint16_t CAOrg = getBits (d, used * 8 + 40, 16); - - ServiceComponent *packetComp = findPacketComponent(SCId); - - used += 56 / 8; - if (packetComp) { - packetComp->subchannelId = SubChId; - packetComp->DSCTy = DSCTy; - packetComp->DGflag = DGflag; - packetComp->packetAddress = packetAddress; - } - return used; -} - -void FIBProcessor::FIG0Extension5 (uint8_t *d) -{ - int16_t used = 2; // offset in bytes - int16_t Length = getBits_5 (d, 3); - - while (used < Length) { - used = HandleFIG0Extension5 (d, used); - } -} - -int16_t FIBProcessor::HandleFIG0Extension5(uint8_t* d, int16_t offset) -{ - int16_t loffset = offset * 8; - uint8_t lsFlag = getBits_1 (d, loffset); - int16_t subChId, serviceComp, language; - - if (lsFlag == 0) { // short form - if (getBits_1 (d, loffset + 1) == 0) { - subChId = getBits_6 (d, loffset + 2); - language = getBits_8 (d, loffset + 8); - subChannels[subChId].language = language; - } - loffset += 16; - } - else { // long form - serviceComp = getBits (d, loffset + 4, 12); - language = getBits_8 (d, loffset + 16); - loffset += 24; - } - (void)serviceComp; - - return loffset / 8; -} - -void FIBProcessor::FIG0Extension8 (uint8_t *d) -{ - int16_t used = 2; // offset in bytes - int16_t Length = getBits_5 (d, 3); - uint8_t PD_bit = getBits_1 (d, 8 + 2); - - while (used < Length) { - used = HandleFIG0Extension8 (d, used, PD_bit); - } -} - -int16_t FIBProcessor::HandleFIG0Extension8( - uint8_t *d, - int16_t used, - uint8_t pdBit) -{ - int16_t lOffset = used * 8; - uint32_t SId = getBits(d, lOffset, pdBit == 1 ? 32 : 16); - lOffset += (pdBit == 1 ? 32 : 16); - - uint8_t extensionFlag; - - extensionFlag = getBits_1(d, lOffset); - uint16_t SCIds = getBits_4(d, lOffset + 4); - lOffset += 4; - - uint8_t lsFlag = getBits_1(d, lOffset); - if (lsFlag == 1) { - //int16_t SCid = getBits(d, lOffset + 4, 12); - lOffset += 16; - // if (findPacketComponent ((SCIds << 4) | SCid) != NULL) { - // std::clog << "fib-processor:" << "packet component bestaat !!\n") << std::endl; - // } - } - else { - //int16_t SubChId = getBits_6(d, lOffset + 4); - lOffset += 8; - } - - if (extensionFlag) { - lOffset += 8; // skip Rfa - } - (void)SId; - (void)SCIds; - return lOffset / 8; -} - -// FIG0/9 and FIG0/10 are copied from the work of -// Michael Hoehn -void FIBProcessor::FIG0Extension9(uint8_t *d) -{ - int16_t offset = 16; - - dateTime.hourOffset = (getBits_1 (d, offset + 2) == 1) ? - -1 * getBits_4 (d, offset + 3): - getBits_4 (d, offset + 3); - dateTime.minuteOffset = (getBits_1 (d, offset + 7) == 1) ? 30 : 0; - timeOffsetReceived = true; - - ensembleEcc = getBits(d, offset + 8, 8); -} - -void FIBProcessor::FIG0Extension10(uint8_t *fig) -{ - int16_t offset = 16; - int32_t mjd = getBits(fig, offset + 1, 17); - // Convert Modified Julian Date (according to wikipedia) - int32_t J = mjd + 2400001; - int32_t j = J + 32044; - int32_t g = j / 146097; - int32_t dg = j % 146097; - int32_t c = ((dg / 36524) + 1) * 3 / 4; - int32_t dc = dg - c * 36524; - int32_t b = dc / 1461; - int32_t db = dc%1461; - int32_t a = ((db / 365) + 1) * 3 / 4; - int32_t da = db - a * 365; - int32_t y = g * 400 + c * 100 + b * 4 + a; - int32_t m = ((da * 5 + 308) / 153) - 2; - int32_t d = da - ((m + 4) * 153 / 5) + 122; - int32_t Y = y - 4800 + ((m + 2) / 12); - int32_t M = ((m + 2) % 12) + 1; - int32_t D = d + 1; - - dateTime.year = Y; - dateTime.month = M; - dateTime.day = D; - dateTime.hour = getBits_5(fig, offset + 21); - if (getBits_6(fig, offset + 26) != dateTime.minutes) - dateTime.seconds = 0; // handle overflow - - dateTime.minutes = getBits_6(fig, offset + 26); - if (fig [offset + 20] == 1) { - dateTime.seconds = getBits_6(fig, offset + 32); - } - - if (timeOffsetReceived) { - myRadioInterface.onDateTimeUpdate(dateTime); - } -} - -void FIBProcessor::FIG0Extension13 (uint8_t *d) -{ - int16_t used = 2; // offset in bytes - int16_t Length = getBits_5 (d, 3); - uint8_t PD_bit = getBits_1 (d, 8 + 2); - - while (used < Length) { - used = HandleFIG0Extension13 (d, used, PD_bit); - } -} - -int16_t FIBProcessor::HandleFIG0Extension13( - uint8_t *d, - int16_t used, - uint8_t pdBit) -{ - int16_t lOffset = used * 8; - uint32_t SId = getBits(d, lOffset, pdBit == 1 ? 32 : 16); - uint16_t SCIds; - int16_t NoApplications; - int16_t i; - - lOffset += pdBit == 1 ? 32 : 16; - SCIds = getBits_4 (d, lOffset); - NoApplications = getBits_4 (d, lOffset + 4); - lOffset += 8; - -// std::clog << "fib-processor: HandleFIG0Extension13 NoApplications " << NoApplications << " "; - - for (i = 0; i < NoApplications; i++) { - int16_t appType = getBits (d, lOffset, 11); - int16_t length = getBits_5 (d, lOffset + 11); - lOffset += (11 + 5 + 8 * length); -// std::clog << " "; - switch (appType) { - case 0x000: // reserved for future use - case 0x001: // not used - break; - - case 0x002: // MOT slideshow -// std::clog << "MOT slideshow"; break; - case 0x003: // MOT Broadcast Web Site -// std::clog << "MOT Broadcast Web Site "; break; - case 0x004: // TPEG -// std::clog << "TPEG length " << length; break; - case 0x005: // DGPS -// std::clog << "DGPS"; break; - case 0x006: // TMC -// std::clog << "TMC "; break; - case 0x007: // EPG -// std::clog << "EPG length " << length; break; - case 0x008: // DAB Java -// std::clog << "DAB Java"; break; - case 0x009: // DMB -// std::clog << "DMB"; break; - case 0x00a: // IPDC services -// std::clog << "IPDC services"; break; - case 0x00b: // Voice applications -// std::clog << "Voice applications"; break; - case 0x00c: // Middleware -// std::clog << "Middleware"; break; - case 0x00d: // Filecasting -// std::clog << "Filecasting"; break; - case 0x44a: // Journaline -// std::clog << "Journaline"; break; - - default: - break; - } - } - -// std::clog << std::endl; - - (void)SId; - (void)SCIds; - return lOffset / 8; -} - -void FIBProcessor::FIG0Extension14 (uint8_t *d) -{ - int16_t length = getBits_5 (d, 3); // in Bytes - int16_t used = 2; // in Bytes - - while (used < length) { - int16_t subChId = getBits_6 (d, used * 8); - uint8_t fecScheme = getBits_2 (d, used * 8 + 6); - used = used + 1; - - for (int i = 0; i < 64; i++) { - if (subChannels[i].subChId == subChId) { - subChannels[i].fecScheme = fecScheme; - } - } - - } -} - -void FIBProcessor::FIG0Extension17(uint8_t *d) -{ - int16_t length = getBits_5 (d, 3); - int16_t offset = 16; - Service *s; - - while (offset < length * 8) { - uint16_t SId = getBits (d, offset, 16); - bool L_flag = getBits_1 (d, offset + 18); - bool CC_flag = getBits_1 (d, offset + 19); - int16_t type; - int16_t Language = 0x00; // init with unknown language - s = findServiceId(SId); - if (L_flag) { // language field present - Language = getBits_8 (d, offset + 24); - if (s) { - s->language = Language; - } - offset += 8; - } - - type = getBits_5 (d, offset + 27); - if (s) { - s->programType = type; - } - if (CC_flag) { // cc flag - offset += 40; - } - else { - offset += 32; - } - } -} - -void FIBProcessor::FIG0Extension18(uint8_t *d) -{ - int16_t offset = 16; // bits - uint16_t SId, AsuFlags; - int16_t Length = getBits_5 (d, 3); - - while (offset / 8 < Length - 1 ) { - int16_t NumClusters = getBits_5 (d, offset + 35); - SId = getBits (d, offset, 16); - AsuFlags = getBits (d, offset + 16, 16); - // std::clog << "fib-processor:" << "Announcement %d for SId %d with %d clusters\n", - // AsuFlags, SId, NumClusters) << std::endl; - offset += 40 + NumClusters * 8; - } - (void)SId; - (void)AsuFlags; -} - -void FIBProcessor::FIG0Extension19(uint8_t *d) -{ - int16_t offset = 16; // bits - int16_t Length = getBits_5 (d, 3); - uint8_t region_Id_Lower; - - while (offset / 8 < Length - 1) { - uint8_t clusterId = getBits_8 (d, offset); - bool new_flag = getBits_1(d, offset + 24); - bool region_flag = getBits_1 (d, offset + 25); - uint8_t subChId = getBits_6 (d, offset + 26); - - uint16_t aswFlags = getBits (d, offset + 8, 16); - // std::clog << "fib-processor:" << - // "%s %s Announcement %d for Cluster %2u on SubCh %2u ", - // ((new_flag==1)?"new":"old"), - // ((region_flag==1)?"regional":""), - // aswFlags, clusterId,subChId) << std::endl; - if (region_flag) { - region_Id_Lower = getBits_6 (d, offset + 34); - offset += 40; - // fprintf(stderr,"for region %u",region_Id_Lower); - } - else { - offset += 32; - } - - // fprintf(stderr,"\n"); - (void)clusterId; - (void)new_flag; - (void)subChId; - (void)aswFlags; - } - (void)region_Id_Lower; -} - -void FIBProcessor::FIG0Extension21(uint8_t *d) -{ - // std::clog << "fib-processor:" << "Frequency information\n") << std::endl; - (void)d; -} - -void FIBProcessor::FIG0Extension22(uint8_t *d) -{ - int16_t Length = getBits_5 (d, 3); - int16_t offset = 16; // on bits - int16_t used = 2; - - while (used < Length) { - used = HandleFIG0Extension22 (d, used); - } - (void)offset; -} - -int16_t FIBProcessor::HandleFIG0Extension22(uint8_t *d, int16_t used) -{ - uint8_t MS; - int16_t mainId; - int16_t noSubfields; - - mainId = getBits_7 (d, used * 8 + 1); - (void)mainId; - MS = getBits_1 (d, used * 8); - if (MS == 0) { // fixed size - int16_t latitudeCoarse = getBits (d, used * 8 + 8, 16); - int16_t longitudeCoarse = getBits (d, used * 8 + 24, 16); - // std::clog << "fib-processor:" << "Id = %d, (%d %d)\n", mainId, - // latitudeCoarse, longitudeCoarse) << std::endl; - (void)latitudeCoarse; - (void)longitudeCoarse; - return used + 48 / 6; - } - // MS == 1 - - noSubfields = getBits_3 (d, used * 8 + 13); - // std::clog << "fib-processor:" << "Id = %d, subfields = %d\n", mainId, noSubfields) << std::endl; - used += (16 + noSubfields * 48) / 8; - - return used; -} - -// FIG 1 - Labels -void FIBProcessor::process_FIG1(uint8_t *d) -{ - uint32_t SId = 0; - int16_t offset = 0; - Service *service; - ServiceComponent *component; - uint8_t pd_flag; - uint8_t SCidS; - char label[17]; - - // FIG 1 first byte - const uint8_t charSet = getBits_4(d, 8); - const uint8_t oe = getBits_1(d, 8 + 4); - const uint8_t extension = getBits_3(d, 8 + 5); - label[16] = 0x00; - if (oe == 1) { - return; - } - - switch (extension) { - case 0: // ensemble label - { - const uint32_t EId = getBits(d, 16, 16); - offset = 32; - for (int i = 0; i < 16; i ++) { - label[i] = getBits_8 (d, offset); - offset += 8; - } - // std::clog << "fib-processor:" << "Ensemblename: " << label << std::endl; - if (!oe and EId == ensembleId) { - ensembleLabel.fig1_flag = getBits(d, offset, 16); - ensembleLabel.fig1_label = label; - ensembleLabel.setCharset(charSet); - myRadioInterface.onSetEnsembleLabel(ensembleLabel); - } - break; - } - - case 1: // 16 bit Identifier field for service label - SId = getBits(d, 16, 16); - offset = 32; - service = findServiceId(SId); - if (service) { - for (int i = 0; i < 16; i++) { - label[i] = getBits_8(d, offset); - offset += 8; - } - service->serviceLabel.fig1_flag = getBits(d, offset, 16); - service->serviceLabel.fig1_label = label; - service->serviceLabel.setCharset(charSet); - - // MB: Added - myRadioInterface.onSetServiceLabel(SId, service->serviceLabel); - - // std::clog << "fib-processor:" << "FIG1/1: SId = %4x\t%s\n", SId, label) << std::endl; - } - break; - - /* - case 3: // Region label - //uint8_t region_id = getBits_6 (d, 16 + 2); - offset = 24; - for (int i = 0; i < 16; i ++) { - label[i] = getBits_8 (d, offset + 8 * i); - } - - // std::clog << "fib-processor:" << "FIG1/3: RegionID = %2x\t%s\n", region_id, label) << std::endl; - break; - */ - - case 4: // Component label - pd_flag = getBits(d, 16, 1); - SCidS = getBits(d, 20, 4); - if (pd_flag) { // 32 bit identifier field for service component label - SId = getBits(d, 24, 32); - offset = 56; - } - else { // 16 bit identifier field for service component label - SId = getBits(d, 24, 16); - offset = 40; - } - - for (int i = 0; i < 16; i ++) { - label[i] = getBits_8 (d, offset); - offset += 8; - } - - component = findComponent(SId, SCidS); - if (component) { - component->componentLabel.fig1_flag = getBits(d, offset, 16); - component->componentLabel.setCharset(charSet); - component->componentLabel.fig1_label = label; - } - // std::clog << "fib-processor:" << "FIG1/4: Sid = %8x\tp/d=%d\tSCidS=%1X\tflag=%8X\t%s\n", - // SId, pd_flag, SCidS, flagfield, label) << std::endl; - break; - - - case 5: // 32 bit Identifier field for service label - SId = getBits(d, 16, 32); - offset = 48; - service = findServiceId(SId); - if (service) { - for (int i = 0; i < 16; i ++) { - label[i] = getBits_8(d, offset); - offset += 8; - } - service->serviceLabel.fig1_flag = getBits(d, offset, 16); - service->serviceLabel.fig1_label = label; - service->serviceLabel.setCharset(charSet); - - // MB: Added - myRadioInterface.onSetServiceLabel(SId, service->serviceLabel); - -#ifdef MSC_DATA__ - myRadioInterface.onServiceDetected(SId); -#endif - } - break; - - /* - case 6: // XPAD label - uint8_t XPAD_aid; - pd_flag = getBits(d, 16, 1); - SCidS = getBits(d, 20, 4); - if (pd_flag) { // 32 bits identifier for XPAD label - SId = getBits(d, 24, 32); - XPAD_aid = getBits(d, 59, 5); - offset = 64; - } - else { // 16 bit identifier for XPAD label - SId = getBits(d, 24, 16); - XPAD_aid = getBits(d, 43, 5); - offset = 48; - } - - for (int i = 0; i < 16; i ++) { - label[i] = getBits_8 (d, offset + 8 * i); - } - - // fprintf(stderr, "fib-processor:" - // "FIG1/6: SId = %8x\tp/d = %d\t SCidS = %1X\tXPAD_aid = %2u\t%s\n", - // SId, pd_flag, SCidS, XPAD_aid, label) << std::endl; - break; - */ - - default: - // std::clog << "fib-processor:" << "FIG1/%d: not handled now\n", extension) << std::endl; - break; - } -} - -static void handle_ext_label_data_field(const uint8_t *f, uint8_t len_bytes, - uint8_t toggle_flag, uint8_t segment_index, uint8_t rfu, - DabLabel& label) -{ - if (label.toggle_flag != toggle_flag) { - label.segments.clear(); - label.extended_label_charset = CharacterSet::Undefined; - label.toggle_flag = toggle_flag; - } - - size_t len_character_field = len_bytes; - - if (segment_index == 0) { - // Only if it's the first segment - const uint8_t encoding_flag = (f[0] & 0x80) >> 7; - const uint8_t segment_count = (f[0] & 0x70) >> 4; - label.segment_count = segment_count + 1; - - if (encoding_flag) { - label.extended_label_charset = CharacterSet::UnicodeUcs2; - } - else { - label.extended_label_charset = CharacterSet::UnicodeUtf8; - } - - if (rfu == 0) { - // const uint8_t rfa = (f[0] & 0x0F); - // const uint16_t char_flag = f[1] * 256 + f[2]; - - if (len_bytes <= 3) { - throw std::runtime_error("FIG2 label length too short"); - } - - f += 3; - len_character_field -= 3; - } - else { - // ETSI TS 103 176 draft V2.2.1 (2018-08) gives a new meaning to rfu - // TODO const uint8_t text_control = (f[0] & 0x0F); - - if (len_bytes <= 1) { - throw std::runtime_error("FIG2 label length too short"); - } - - f += 1; - len_character_field -= 1; - } - - label.fig2_rfu = rfu; - } - - std::vector labelbytes(f, f + len_character_field); - label.segments[segment_index] = labelbytes; -} - -// UTF-8 or UCS2 Labels -void FIBProcessor::process_FIG2(uint8_t *d) -{ - // In order to reuse code with etisnoop, convert - // the bit-vector into a byte-vector - std::vector fig_bytes; - for (size_t i = 0; i < 30; i++) { - fig_bytes.push_back(getBits_8(d, 8*i)); - } - - uint8_t *f = fig_bytes.data(); - - const uint8_t figlen = f[0] & 0x1F; - f++; - - const uint8_t toggle_flag = (f[0] & 0x80) >> 7; - const uint8_t segment_index = (f[0] & 0x70) >> 4; - const uint16_t rfu = (f[0] & 0x08) >> 3; - const uint16_t ext = f[0] & 0x07; - - size_t identifier_len; - switch (ext) { - case 0: // Ensemble label - identifier_len = 2; - break; - case 1: // Programme service label - identifier_len = 2; - break; - case 4: // Service component label - { - uint8_t pd = (f[1] & 0x80) >> 7; - identifier_len = (pd == 0) ? 3 : 5; - break; - } - case 5: // Data service label - identifier_len = 4; - break; - default: - return; // Unsupported - } - - const size_t header_length = 1; // FIG data field header - - const uint8_t *figdata = f + header_length + identifier_len; - const size_t data_len_bytes = figlen - header_length - identifier_len; - - // ext is followed by Identifier field of Type 2 field, - // whose length depends on ext - switch (ext) { - case 0: // Ensemble label - { // ETSI EN 300 401 8.1.13 - uint16_t eid = f[1] * 256 + f[2]; - if (figlen <= header_length + identifier_len) { - //std::clog << "FIG2/0 length error " << (int)figlen << std::endl; - } - else if (eid == ensembleId) { - handle_ext_label_data_field(figdata, data_len_bytes, - toggle_flag, segment_index, rfu, ensembleLabel); - } - } - break; - - case 1: // Programme service label - { // ETSI EN 300 401 8.1.14.1 - uint16_t sid = f[1] * 256 + f[2]; - if (figlen <= header_length + identifier_len) { - //std::clog << "FIG2/1 length error " << (int)figlen << std::endl; - } - else { - auto *service = findServiceId(sid); - if (service) { - handle_ext_label_data_field(figdata, data_len_bytes, - toggle_flag, segment_index, rfu, service->serviceLabel); - } - } - } - break; - - case 4: // Service component label - { // ETSI EN 300 401 8.1.14.3 - uint32_t sid; - uint8_t pd = (f[1] & 0x80) >> 7; - uint8_t SCIdS = f[1] & 0x0F; - if (pd == 0) { - sid = f[2] * 256 + \ - f[3]; - } - else { - sid = ((uint32_t)f[2] << 24) | - ((uint32_t)f[3] << 16) | - ((uint32_t)f[4] << 8) | - ((uint32_t)f[5]); - } - if (figlen <= header_length + identifier_len) { - //std::clog << "FIG2/4 length error " << (int)figlen << std::endl; - } - else { - auto *component = findComponent(sid, SCIdS); - if (component) { - handle_ext_label_data_field(figdata, data_len_bytes, - toggle_flag, segment_index, rfu, component->componentLabel); - } - } - } - break; - - case 5: // Data service label - { // ETSI EN 300 401 8.1.14.2 - const uint32_t sid = - ((uint32_t)f[1] << 24) | - ((uint32_t)f[2] << 16) | - ((uint32_t)f[3] << 8) | - ((uint32_t)f[4]); - - if (figlen <= header_length + identifier_len) { - //std::clog << "FIG2/5 length error " << (int)figlen << std::endl; - } - else { - auto *service = findServiceId(sid); - if (service) { - handle_ext_label_data_field(figdata, data_len_bytes, - toggle_flag, segment_index, rfu, service->serviceLabel); - } - } - } - break; - } -} - -// locate a reference to the entry for the Service serviceId -Service *FIBProcessor::findServiceId(uint32_t serviceId) -{ - for (size_t i = 0; i < services.size(); i++) { - if (services[i].serviceId == serviceId) { - return &services[i]; - } - } - - return nullptr; -} - -ServiceComponent *FIBProcessor::findComponent(uint32_t serviceId, int16_t SCIdS) -{ - auto comp = std::find_if(components.begin(), components.end(), - [&](const ServiceComponent& sc) { - return sc.SId == serviceId && sc.componentNr == SCIdS; - }); - - if (comp == components.end()) { - return nullptr; - } - else { - return &(*comp); - } -} - -ServiceComponent *FIBProcessor::findPacketComponent(int16_t SCId) -{ - for (auto& component : components) { - if (component.TMid != 03) { - continue; - } - if (component.SCId == SCId) { - return &component; - } - } - return nullptr; -} - -// bindAudioService is the main processor for - what the name suggests - -// connecting the description of audioservices to a SID -void FIBProcessor::bindAudioService( - int8_t TMid, - uint32_t SId, - int16_t compnr, - int16_t subChId, - int16_t ps_flag, - int16_t ASCTy) -{ - Service *s = findServiceId(SId); - if (!s) return; - - if (std::find_if(components.begin(), components.end(), - [&](const ServiceComponent& sc) { - return sc.SId == s->serviceId && sc.componentNr == compnr; - }) == components.end()) { - ServiceComponent newcomp; - newcomp.TMid = TMid; - newcomp.componentNr = compnr; - newcomp.SId = SId; - newcomp.subchannelId = subChId; - newcomp.PS_flag = ps_flag; - newcomp.ASCTy = ASCTy; - components.push_back(newcomp); - - // std::clog << "fib-processor:" << "service %8x (comp %d) is audio\n", SId, compnr) << std::endl; - } -} - -void FIBProcessor::bindDataStreamService( - int8_t TMid, - uint32_t SId, - int16_t compnr, - int16_t subChId, - int16_t ps_flag, - int16_t DSCTy) -{ - Service *s = findServiceId(SId); - if (!s) return; - - if (std::find_if(components.begin(), components.end(), - [&](const ServiceComponent& sc) { - return sc.SId == s->serviceId && sc.componentNr == compnr; - }) == components.end()) { - ServiceComponent newcomp; - newcomp.TMid = TMid; - newcomp.SId = SId; - newcomp.subchannelId = subChId; - newcomp.componentNr = compnr; - newcomp.PS_flag = ps_flag; - newcomp.DSCTy = DSCTy; - components.push_back(newcomp); - - // std::clog << "fib-processor:" << "service %8x (comp %d) is packet\n", SId, compnr) << std::endl; - } -} - -// bindPacketService is the main processor for - what the name suggests - -// connecting the service component defining the service to the SId, -/// Note that the subchannel is assigned through a FIG0/3 -void FIBProcessor::bindPacketService( - int8_t TMid, - uint32_t SId, - int16_t compnr, - int16_t SCId, - int16_t ps_flag, - int16_t CAflag) -{ - Service *s = findServiceId(SId); - if (!s) return; - - if (std::find_if(components.begin(), components.end(), - [&](const ServiceComponent& sc) { - return sc.SId == s->serviceId && sc.componentNr == compnr; - }) == components.end()) { - ServiceComponent newcomp; - newcomp.TMid = TMid; - newcomp.SId = SId; - newcomp.componentNr = compnr; - newcomp.SCId = SCId; - newcomp.PS_flag = ps_flag; - newcomp.CAflag = CAflag; - components.push_back(newcomp); - - // std::clog << "fib-processor:" << "service %8x (comp %d) is packet\n", SId, compnr) << std::endl; - } -} - -void FIBProcessor::dropService(uint32_t SId) -{ - std::stringstream ss; - ss << "Dropping service " << SId; - - services.erase(std::remove_if(services.begin(), services.end(), - [&](const Service& s) { - return s.serviceId == SId; - } - ), services.end()); - - components.erase(std::remove_if(components.begin(), components.end(), - [&](const ServiceComponent& c) { - const bool drop = c.SId == SId; - if (drop) { - ss << ", comp " << c.componentNr; - } - return drop; - } - ), components.end()); - - // Check for orphaned subchannels - for (auto& sub : subChannels) { - if (sub.subChId == -1) { - continue; - } - - auto c_it = std::find_if(components.begin(), components.end(), - [&](const ServiceComponent& c) { - return c.subchannelId == sub.subChId; - }); - - const bool drop = c_it == components.end(); - if (drop) { - ss << ", subch " << sub.subChId; - sub.subChId = -1; - } - } - - //std::clog << ss.str() << std::endl; -} - -void FIBProcessor::clearEnsemble() -{ - std::lock_guard lock(mutex); - components.clear(); - subChannels.resize(64); - services.clear(); - serviceRepeatCount.clear(); - timeLastServiceDecrement = std::chrono::steady_clock::now(); - timeLastFCT0Frame = std::chrono::system_clock::now(); -} - -std::vector FIBProcessor::getServiceList() const -{ - std::lock_guard lock(mutex); - return services; -} - -Service FIBProcessor::getService(uint32_t sId) const -{ - std::lock_guard lock(mutex); - - auto srv = std::find_if(services.begin(), services.end(), - [&](const Service& s) { - return s.serviceId == sId; - }); - - if (srv != services.end()) { - return *srv; - } - else { - return Service(0); - } -} - -std::list FIBProcessor::getComponents(const Service& s) const -{ - std::list c; - std::lock_guard lock(mutex); - for (const auto& component : components) { - if (component.SId == s.serviceId) { - c.push_back(component); - } - } - - return c; -} - -Subchannel FIBProcessor::getSubchannel(const ServiceComponent& sc) const -{ - std::lock_guard lock(mutex); - return subChannels.at(sc.subchannelId); -} - -uint16_t FIBProcessor::getEnsembleId() const -{ - std::lock_guard lock(mutex); - return ensembleId; -} - -uint8_t FIBProcessor::getEnsembleEcc() const -{ - std::lock_guard lock(mutex); - return ensembleEcc; -} - -DabLabel FIBProcessor::getEnsembleLabel() const -{ - std::lock_guard lock(mutex); - return ensembleLabel; -} - -std::chrono::system_clock::time_point FIBProcessor::getTimeLastFCT0Frame() const -{ - std::lock_guard lock(mutex); - return timeLastFCT0Frame; -} +/* + * Copyright (C) 2020 + * Matthias P. Braendli (matthias.braendli@mpb.li) + * + * Copyright (C) 2014 + * Jan van Katwijk (J.vanKatwijk@gmail.com) + * Lazy Chair Programming + * + * This file is part of the SDR-J (JSDR). + * SDR-J is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * SDR-J 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with SDR-J; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * fib and fig processor + */ +#include +#include +#include +#include +#include + +#include "fib-processor.h" +#include "charsets.h" +#include "MathHelper.h" + +FIBProcessor::FIBProcessor(RadioControllerInterface& mr) : + myRadioInterface(mr) +{ + clearEnsemble(); +} + +// FIB's are segments of 256 bits. When here, we already +// passed the crc and we start unpacking into FIGs +// This is merely a dispatcher +void FIBProcessor::processFIB(uint8_t *p, uint16_t fib) +{ + int8_t processedBytes = 0; + uint8_t *d = p; + + std::lock_guard lock(mutex); + + (void)fib; + while (processedBytes < 30) { + const uint8_t FIGtype = getBits_3 (d, 0); + switch (FIGtype) { + case 0: + process_FIG0(d); + break; + + case 1: + process_FIG1(d); + break; + + case 2: + process_FIG2(d); + break; + + case 7: + return; + + default: + //std::clog << "FIG%d present" << FIGtype << std::endl; + break; + } + // Thanks to Ronny Kunze, who discovered that I used + // a p rather than a d + processedBytes += getBits_5 (d, 3) + 1; + d = p + processedBytes * 8; + } +} +// +// Handle ensemble is all through FIG0 +// +void FIBProcessor::process_FIG0 (uint8_t *d) +{ + uint8_t extension = getBits_5 (d, 8 + 3); + //uint8_t CN = getBits_1 (d, 8 + 0); + + switch (extension) { + case 0: FIG0Extension0 (d); break; + case 1: FIG0Extension1 (d); break; + case 2: FIG0Extension2 (d); break; + case 3: FIG0Extension3 (d); break; + case 5: FIG0Extension5 (d); break; + case 8: FIG0Extension8 (d); break; + case 9: FIG0Extension9 (d); break; + case 10: FIG0Extension10 (d); break; + case 14: FIG0Extension14 (d); break; + case 13: FIG0Extension13 (d); break; + case 17: FIG0Extension17 (d); break; + case 18: FIG0Extension18 (d); break; + case 19: FIG0Extension19 (d); break; + case 21: FIG0Extension21 (d); break; + case 22: FIG0Extension22 (d); break; + default: + // std::clog << "fib-processor:" << "FIG0/%d passed by\n", extension) << std::endl; + break; + } +} + + +// FIG0/0 indicated a change in channel organization +// we are not equipped for that, so we just return +// control to the init +void FIBProcessor::FIG0Extension0 (uint8_t *d) +{ + uint8_t changeflag; + uint16_t highpart, lowpart; + int16_t occurrenceChange; + uint8_t CN = getBits_1 (d, 8 + 0); + (void)CN; + + uint16_t eId = getBits(d, 16, 16); + + if (ensembleId != eId) { + ensembleId = eId; + myRadioInterface.onNewEnsemble(ensembleId); + } + + changeflag = getBits_2 (d, 16 + 16); + + highpart = getBits_5 (d, 16 + 19) % 20; + (void)highpart; + lowpart = getBits_8 (d, 16 + 24) % 250; + (void)lowpart; + occurrenceChange = getBits_8 (d, 16 + 32); + (void)occurrenceChange; + + // In transmission mode I, because four ETI frames make one transmission frame, we will + // see lowpart == 0 only every twelve seconds, and not 6 as expected by the 250 overflow value. + if (lowpart == 0) { + timeLastFCT0Frame = std::chrono::system_clock::now(); + } + + if (changeflag == 0) + return; + // else if (changeflag == 1) { + // std::clog << "fib-processor:" << "Changes in sub channel organization\n") << std::endl; + // std::clog << "fib-processor:" << "cifcount = %d\n", highpart * 250 + lowpart) << std::endl; + // std::clog << "fib-processor:" << "Change happening in %d CIFs\n", occurrenceChange) << std::endl; + // } + // else if (changeflag == 3) { + // std::clog << "fib-processor:" << "Changes in subchannel and service organization\n") << std::endl; + // std::clog << "fib-processor:" << "cifcount = %d\n", highpart * 250 + lowpart) << std::endl; + // std::clog << "fib-processor:" << "Change happening in %d CIFs\n", occurrenceChange) << std::endl; + // } + // std::clog << "fib-processor: " << "changes in config not supported, choose again" << std::endl; +} + +// FIG0 extension 1 creates a mapping between the +// sub channel identifications and the positions in the +// relevant CIF. +void FIBProcessor::FIG0Extension1 (uint8_t *d) +{ + int16_t used = 2; // offset in bytes + int16_t Length = getBits_5 (d, 3); + uint8_t PD_bit = getBits_1 (d, 8 + 2); + //uint8_t CN = getBits_1 (d, 8 + 0); + + while (used < Length - 1) + used = HandleFIG0Extension1 (d, used, PD_bit); +} + +// defining the channels +int16_t FIBProcessor::HandleFIG0Extension1( + uint8_t *d, + int16_t offset, + uint8_t pd) +{ + int16_t bitOffset = offset * 8; + const int16_t subChId = getBits_6 (d, bitOffset); + const int16_t startAdr = getBits(d, bitOffset + 6, 10); + subChannels[subChId].programmeNotData = pd; + subChannels[subChId].subChId = subChId; + subChannels[subChId].startAddr = startAdr; + if (getBits_1 (d, bitOffset + 16) == 0) { // UEP, short form + int16_t tableIx = getBits_6 (d, bitOffset + 18); + auto& ps = subChannels[subChId].protectionSettings; + ps.uepTableIndex = tableIx; + ps.shortForm = true; + ps.uepLevel = ProtLevel[tableIx][1]; + + subChannels[subChId].length = ProtLevel[tableIx][0]; + bitOffset += 24; + } + else { // EEP, long form + auto& ps = subChannels[subChId].protectionSettings; + ps.shortForm = false; + int16_t option = getBits_3(d, bitOffset + 17); + if (option == 0) { + ps.eepProfile = EEPProtectionProfile::EEP_A; + } + else if (option == 1) { + ps.eepProfile = EEPProtectionProfile::EEP_B; + } + + if (option == 0 or // EEP-A protection + option == 1) { // EEP-B protection + int16_t protLevel = getBits_2(d, bitOffset + 20); + switch (protLevel) { + case 0: + ps.eepLevel = EEPProtectionLevel::EEP_1; + break; + case 1: + ps.eepLevel = EEPProtectionLevel::EEP_2; + break; + case 2: + ps.eepLevel = EEPProtectionLevel::EEP_3; + break; + case 3: + ps.eepLevel = EEPProtectionLevel::EEP_4; + break; + default: + //std::clog << "Warning, FIG0/1 for " << subChId << + // " has invalid EEP protection level " << protLevel << + // std::endl; + break; + } + + int16_t subChanSize = getBits(d, bitOffset + 22, 10); + subChannels[subChId].length = subChanSize; + } + else { + //std::clog << "Warning, FIG0/1 for " << subChId << + // " has invalid protection option " << option << std::endl; + } + + bitOffset += 32; + } + + return bitOffset / 8; // we return bytes +} + +void FIBProcessor::FIG0Extension2 (uint8_t *d) +{ + int16_t used = 2; // offset in bytes + int16_t Length = getBits_5 (d, 3); + uint8_t PD_bit = getBits_1 (d, 8 + 2); + uint8_t CN = getBits_1 (d, 8 + 0); + + while (used < Length) { + used = HandleFIG0Extension2(d, used, CN, PD_bit); + } +} + +// Note Offset is in bytes +// With FIG0/2 we bind the channels to Service Ids +int16_t FIBProcessor::HandleFIG0Extension2( + uint8_t *d, + int16_t offset, + uint8_t cn, + uint8_t pd) +{ + (void)cn; + int16_t lOffset = 8 * offset; + int16_t i; + uint8_t ecc; + uint8_t cId; + uint32_t SId; + int16_t numberofComponents; + + if (pd == 1) { // long Sid + ecc = getBits_8(d, lOffset); (void)ecc; + cId = getBits_4(d, lOffset + 1); + SId = getBits(d, lOffset, 32); + lOffset += 32; + } + else { + cId = getBits_4(d, lOffset); (void)cId; + SId = getBits(d, lOffset + 4, 12); + SId = getBits(d, lOffset, 16); + lOffset += 16; + } + + // Keep track how often we see a service using a saturating counter. + // Every time a service is signalled, we increment the counter. + // If the counter is >= 2, we consider the service. Every second, we + // decrement all counters by one. + // This avoids that misdecoded services appear and stay in the list. + using namespace std::chrono; + const auto now = steady_clock::now(); + if (timeLastServiceDecrement + seconds(1) < now) { + + auto it = serviceRepeatCount.begin(); + while (it != serviceRepeatCount.end()) { + if (it->second > 0) { + it->second--; + ++it; + } + else if (it->second == 0) { + dropService(it->second); + it = serviceRepeatCount.erase(it); + } + else { + ++it; + } + } + + timeLastServiceDecrement = now; + +#if 0 + std::stringstream ss; + ss << "Counters: "; + for (auto& c : serviceRepeatCount) { + ss << " " << c.first << ":" << (int)c.second; + } + std::cerr << ss.str() << std::endl; +#endif + } + + if (serviceRepeatCount[SId] < 4) { + serviceRepeatCount[SId]++; + } + + if (findServiceId(SId) == nullptr and serviceRepeatCount[SId] >= 2) { + services.emplace_back(SId); + myRadioInterface.onServiceDetected(SId); + } + + numberofComponents = getBits_4(d, lOffset + 4); + lOffset += 8; + + for (i = 0; i < numberofComponents; i ++) { + uint8_t TMid = getBits_2 (d, lOffset); + if (TMid == 00) { // Audio + uint8_t ASCTy = getBits_6 (d, lOffset + 2); + uint8_t SubChId = getBits_6 (d, lOffset + 8); + uint8_t PS_flag = getBits_1 (d, lOffset + 14); + bindAudioService(TMid, SId, i, SubChId, PS_flag, ASCTy); + } + else if (TMid == 1) { // MSC stream data + uint8_t DSCTy = getBits_6 (d, lOffset + 2); + uint8_t SubChId = getBits_6 (d, lOffset + 8); + uint8_t PS_flag = getBits_1 (d, lOffset + 14); + bindDataStreamService(TMid, SId, i, SubChId, PS_flag, DSCTy); + } + else if (TMid == 3) { // MSC packet data + int16_t SCId = getBits (d, lOffset + 2, 12); + uint8_t PS_flag = getBits_1 (d, lOffset + 14); + uint8_t CA_flag = getBits_1 (d, lOffset + 15); + bindPacketService(TMid, SId, i, SCId, PS_flag, CA_flag); + } + else { + // reserved + } + lOffset += 16; + } + return lOffset / 8; // in Bytes +} + +// The Extension 3 of FIG type 0 (FIG 0/3) gives +// additional information about the service component +// description in packet mode. +// manual: page 55 +void FIBProcessor::FIG0Extension3 (uint8_t *d) +{ + int16_t used = 2; + int16_t Length = getBits_5 (d, 3); + + while (used < Length) { + used = HandleFIG0Extension3 (d, used); + } +} + +// DSCTy DataService Component Type +int16_t FIBProcessor::HandleFIG0Extension3(uint8_t *d, int16_t used) +{ + int16_t SCId = getBits (d, used * 8, 12); + //int16_t CAOrgflag = getBits_1 (d, used * 8 + 15); + int16_t DGflag = getBits_1 (d, used * 8 + 16); + int16_t DSCTy = getBits_6 (d, used * 8 + 18); + int16_t SubChId = getBits_6 (d, used * 8 + 24); + int16_t packetAddress = getBits (d, used * 8 + 30, 10); + //uint16_t CAOrg = getBits (d, used * 8 + 40, 16); + + ServiceComponent *packetComp = findPacketComponent(SCId); + + used += 56 / 8; + if (packetComp) { + packetComp->subchannelId = SubChId; + packetComp->DSCTy = DSCTy; + packetComp->DGflag = DGflag; + packetComp->packetAddress = packetAddress; + } + return used; +} + +void FIBProcessor::FIG0Extension5 (uint8_t *d) +{ + int16_t used = 2; // offset in bytes + int16_t Length = getBits_5 (d, 3); + + while (used < Length) { + used = HandleFIG0Extension5 (d, used); + } +} + +int16_t FIBProcessor::HandleFIG0Extension5(uint8_t* d, int16_t offset) +{ + int16_t loffset = offset * 8; + uint8_t lsFlag = getBits_1 (d, loffset); + int16_t subChId, serviceComp, language; + + if (lsFlag == 0) { // short form + if (getBits_1 (d, loffset + 1) == 0) { + subChId = getBits_6 (d, loffset + 2); + language = getBits_8 (d, loffset + 8); + subChannels[subChId].language = language; + } + loffset += 16; + } + else { // long form + serviceComp = getBits (d, loffset + 4, 12); + language = getBits_8 (d, loffset + 16); + loffset += 24; + } + (void)serviceComp; + + return loffset / 8; +} + +void FIBProcessor::FIG0Extension8 (uint8_t *d) +{ + int16_t used = 2; // offset in bytes + int16_t Length = getBits_5 (d, 3); + uint8_t PD_bit = getBits_1 (d, 8 + 2); + + while (used < Length) { + used = HandleFIG0Extension8 (d, used, PD_bit); + } +} + +int16_t FIBProcessor::HandleFIG0Extension8( + uint8_t *d, + int16_t used, + uint8_t pdBit) +{ + int16_t lOffset = used * 8; + uint32_t SId = getBits(d, lOffset, pdBit == 1 ? 32 : 16); + lOffset += (pdBit == 1 ? 32 : 16); + + uint8_t extensionFlag; + + extensionFlag = getBits_1(d, lOffset); + uint16_t SCIds = getBits_4(d, lOffset + 4); + lOffset += 4; + + uint8_t lsFlag = getBits_1(d, lOffset); + if (lsFlag == 1) { + //int16_t SCid = getBits(d, lOffset + 4, 12); + lOffset += 16; + // if (findPacketComponent ((SCIds << 4) | SCid) != NULL) { + // std::clog << "fib-processor:" << "packet component bestaat !!\n") << std::endl; + // } + } + else { + //int16_t SubChId = getBits_6(d, lOffset + 4); + lOffset += 8; + } + + if (extensionFlag) { + lOffset += 8; // skip Rfa + } + (void)SId; + (void)SCIds; + return lOffset / 8; +} + +// FIG0/9 and FIG0/10 are copied from the work of +// Michael Hoehn +void FIBProcessor::FIG0Extension9(uint8_t *d) +{ + int16_t offset = 16; + + dateTime.hourOffset = (getBits_1 (d, offset + 2) == 1) ? + -1 * getBits_4 (d, offset + 3): + getBits_4 (d, offset + 3); + dateTime.minuteOffset = (getBits_1 (d, offset + 7) == 1) ? 30 : 0; + timeOffsetReceived = true; + + ensembleEcc = getBits(d, offset + 8, 8); +} + +void FIBProcessor::FIG0Extension10(uint8_t *fig) +{ + int16_t offset = 16; + int32_t mjd = getBits(fig, offset + 1, 17); + // Convert Modified Julian Date (according to wikipedia) + int32_t J = mjd + 2400001; + int32_t j = J + 32044; + int32_t g = j / 146097; + int32_t dg = j % 146097; + int32_t c = ((dg / 36524) + 1) * 3 / 4; + int32_t dc = dg - c * 36524; + int32_t b = dc / 1461; + int32_t db = dc%1461; + int32_t a = ((db / 365) + 1) * 3 / 4; + int32_t da = db - a * 365; + int32_t y = g * 400 + c * 100 + b * 4 + a; + int32_t m = ((da * 5 + 308) / 153) - 2; + int32_t d = da - ((m + 4) * 153 / 5) + 122; + int32_t Y = y - 4800 + ((m + 2) / 12); + int32_t M = ((m + 2) % 12) + 1; + int32_t D = d + 1; + + dateTime.year = Y; + dateTime.month = M; + dateTime.day = D; + dateTime.hour = getBits_5(fig, offset + 21); + if (getBits_6(fig, offset + 26) != dateTime.minutes) + dateTime.seconds = 0; // handle overflow + + dateTime.minutes = getBits_6(fig, offset + 26); + if (fig [offset + 20] == 1) { + dateTime.seconds = getBits_6(fig, offset + 32); + } + + if (timeOffsetReceived) { + myRadioInterface.onDateTimeUpdate(dateTime); + } +} + +void FIBProcessor::FIG0Extension13 (uint8_t *d) +{ + int16_t used = 2; // offset in bytes + int16_t Length = getBits_5 (d, 3); + uint8_t PD_bit = getBits_1 (d, 8 + 2); + + while (used < Length) { + used = HandleFIG0Extension13 (d, used, PD_bit); + } +} + +int16_t FIBProcessor::HandleFIG0Extension13( + uint8_t *d, + int16_t used, + uint8_t pdBit) +{ + int16_t lOffset = used * 8; + uint32_t SId = getBits(d, lOffset, pdBit == 1 ? 32 : 16); + uint16_t SCIds; + int16_t NoApplications; + int16_t i; + + lOffset += pdBit == 1 ? 32 : 16; + SCIds = getBits_4 (d, lOffset); + NoApplications = getBits_4 (d, lOffset + 4); + lOffset += 8; + +// std::clog << "fib-processor: HandleFIG0Extension13 NoApplications " << NoApplications << " "; + + for (i = 0; i < NoApplications; i++) { + int16_t appType = getBits (d, lOffset, 11); + int16_t length = getBits_5 (d, lOffset + 11); + lOffset += (11 + 5 + 8 * length); +// std::clog << " "; + switch (appType) { + case 0x000: // reserved for future use + case 0x001: // not used + break; + + case 0x002: // MOT slideshow +// std::clog << "MOT slideshow"; break; + case 0x003: // MOT Broadcast Web Site +// std::clog << "MOT Broadcast Web Site "; break; + case 0x004: // TPEG +// std::clog << "TPEG length " << length; break; + case 0x005: // DGPS +// std::clog << "DGPS"; break; + case 0x006: // TMC +// std::clog << "TMC "; break; + case 0x007: // EPG +// std::clog << "EPG length " << length; break; + case 0x008: // DAB Java +// std::clog << "DAB Java"; break; + case 0x009: // DMB +// std::clog << "DMB"; break; + case 0x00a: // IPDC services +// std::clog << "IPDC services"; break; + case 0x00b: // Voice applications +// std::clog << "Voice applications"; break; + case 0x00c: // Middleware +// std::clog << "Middleware"; break; + case 0x00d: // Filecasting +// std::clog << "Filecasting"; break; + case 0x44a: // Journaline +// std::clog << "Journaline"; break; + + default: + break; + } + } + +// std::clog << std::endl; + + (void)SId; + (void)SCIds; + return lOffset / 8; +} + +void FIBProcessor::FIG0Extension14 (uint8_t *d) +{ + int16_t length = getBits_5 (d, 3); // in Bytes + int16_t used = 2; // in Bytes + + while (used < length) { + int16_t subChId = getBits_6 (d, used * 8); + uint8_t fecScheme = getBits_2 (d, used * 8 + 6); + used = used + 1; + + for (int i = 0; i < 64; i++) { + if (subChannels[i].subChId == subChId) { + subChannels[i].fecScheme = fecScheme; + } + } + + } +} + +void FIBProcessor::FIG0Extension17(uint8_t *d) +{ + int16_t length = getBits_5 (d, 3); + int16_t offset = 16; + Service *s; + + while (offset < length * 8) { + uint16_t SId = getBits (d, offset, 16); + bool L_flag = getBits_1 (d, offset + 18); + bool CC_flag = getBits_1 (d, offset + 19); + int16_t type; + int16_t Language = 0x00; // init with unknown language + s = findServiceId(SId); + if (L_flag) { // language field present + Language = getBits_8 (d, offset + 24); + if (s) { + s->language = Language; + } + offset += 8; + } + + type = getBits_5 (d, offset + 27); + if (s) { + s->programType = type; + } + if (CC_flag) { // cc flag + offset += 40; + } + else { + offset += 32; + } + } +} + +void FIBProcessor::FIG0Extension18(uint8_t *d) +{ + int16_t offset = 16; // bits + uint16_t SId, AsuFlags; + int16_t Length = getBits_5 (d, 3); + + while (offset / 8 < Length - 1 ) { + int16_t NumClusters = getBits_5 (d, offset + 35); + SId = getBits (d, offset, 16); + AsuFlags = getBits (d, offset + 16, 16); + // std::clog << "fib-processor:" << "Announcement %d for SId %d with %d clusters\n", + // AsuFlags, SId, NumClusters) << std::endl; + offset += 40 + NumClusters * 8; + } + (void)SId; + (void)AsuFlags; +} + +void FIBProcessor::FIG0Extension19(uint8_t *d) +{ + int16_t offset = 16; // bits + int16_t Length = getBits_5 (d, 3); + uint8_t region_Id_Lower; + + while (offset / 8 < Length - 1) { + uint8_t clusterId = getBits_8 (d, offset); + bool new_flag = getBits_1(d, offset + 24); + bool region_flag = getBits_1 (d, offset + 25); + uint8_t subChId = getBits_6 (d, offset + 26); + + uint16_t aswFlags = getBits (d, offset + 8, 16); + // std::clog << "fib-processor:" << + // "%s %s Announcement %d for Cluster %2u on SubCh %2u ", + // ((new_flag==1)?"new":"old"), + // ((region_flag==1)?"regional":""), + // aswFlags, clusterId,subChId) << std::endl; + if (region_flag) { + region_Id_Lower = getBits_6 (d, offset + 34); + offset += 40; + // fprintf(stderr,"for region %u",region_Id_Lower); + } + else { + offset += 32; + } + + // fprintf(stderr,"\n"); + (void)clusterId; + (void)new_flag; + (void)subChId; + (void)aswFlags; + } + (void)region_Id_Lower; +} + +void FIBProcessor::FIG0Extension21(uint8_t *d) +{ + // std::clog << "fib-processor:" << "Frequency information\n") << std::endl; + (void)d; +} + +void FIBProcessor::FIG0Extension22(uint8_t *d) +{ + int16_t Length = getBits_5 (d, 3); + int16_t offset = 16; // on bits + int16_t used = 2; + + while (used < Length) { + used = HandleFIG0Extension22 (d, used); + } + (void)offset; +} + +int16_t FIBProcessor::HandleFIG0Extension22(uint8_t *d, int16_t used) +{ + uint8_t MS; + int16_t mainId; + int16_t noSubfields; + + mainId = getBits_7 (d, used * 8 + 1); + (void)mainId; + MS = getBits_1 (d, used * 8); + if (MS == 0) { // fixed size + int16_t latitudeCoarse = getBits (d, used * 8 + 8, 16); + int16_t longitudeCoarse = getBits (d, used * 8 + 24, 16); + // std::clog << "fib-processor:" << "Id = %d, (%d %d)\n", mainId, + // latitudeCoarse, longitudeCoarse) << std::endl; + (void)latitudeCoarse; + (void)longitudeCoarse; + return used + 48 / 6; + } + // MS == 1 + + noSubfields = getBits_3 (d, used * 8 + 13); + // std::clog << "fib-processor:" << "Id = %d, subfields = %d\n", mainId, noSubfields) << std::endl; + used += (16 + noSubfields * 48) / 8; + + return used; +} + +// FIG 1 - Labels +void FIBProcessor::process_FIG1(uint8_t *d) +{ + uint32_t SId = 0; + int16_t offset = 0; + Service *service; + ServiceComponent *component; + uint8_t pd_flag; + uint8_t SCidS; + char label[17]; + + // FIG 1 first byte + const uint8_t charSet = getBits_4(d, 8); + const uint8_t oe = getBits_1(d, 8 + 4); + const uint8_t extension = getBits_3(d, 8 + 5); + label[16] = 0x00; + if (oe == 1) { + return; + } + + switch (extension) { + case 0: // ensemble label + { + const uint32_t EId = getBits(d, 16, 16); + offset = 32; + for (int i = 0; i < 16; i ++) { + label[i] = getBits_8 (d, offset); + offset += 8; + } + // std::clog << "fib-processor:" << "Ensemblename: " << label << std::endl; + if (!oe and EId == ensembleId) { + ensembleLabel.fig1_flag = getBits(d, offset, 16); + ensembleLabel.fig1_label = label; + ensembleLabel.setCharset(charSet); + myRadioInterface.onSetEnsembleLabel(ensembleLabel); + } + break; + } + + case 1: // 16 bit Identifier field for service label + SId = getBits(d, 16, 16); + offset = 32; + service = findServiceId(SId); + if (service) { + for (int i = 0; i < 16; i++) { + label[i] = getBits_8(d, offset); + offset += 8; + } + service->serviceLabel.fig1_flag = getBits(d, offset, 16); + service->serviceLabel.fig1_label = label; + service->serviceLabel.setCharset(charSet); + + // MB: Added + myRadioInterface.onSetServiceLabel(SId, service->serviceLabel); + + // std::clog << "fib-processor:" << "FIG1/1: SId = %4x\t%s\n", SId, label) << std::endl; + } + break; + + /* + case 3: // Region label + //uint8_t region_id = getBits_6 (d, 16 + 2); + offset = 24; + for (int i = 0; i < 16; i ++) { + label[i] = getBits_8 (d, offset + 8 * i); + } + + // std::clog << "fib-processor:" << "FIG1/3: RegionID = %2x\t%s\n", region_id, label) << std::endl; + break; + */ + + case 4: // Component label + pd_flag = getBits(d, 16, 1); + SCidS = getBits(d, 20, 4); + if (pd_flag) { // 32 bit identifier field for service component label + SId = getBits(d, 24, 32); + offset = 56; + } + else { // 16 bit identifier field for service component label + SId = getBits(d, 24, 16); + offset = 40; + } + + for (int i = 0; i < 16; i ++) { + label[i] = getBits_8 (d, offset); + offset += 8; + } + + component = findComponent(SId, SCidS); + if (component) { + component->componentLabel.fig1_flag = getBits(d, offset, 16); + component->componentLabel.setCharset(charSet); + component->componentLabel.fig1_label = label; + } + // std::clog << "fib-processor:" << "FIG1/4: Sid = %8x\tp/d=%d\tSCidS=%1X\tflag=%8X\t%s\n", + // SId, pd_flag, SCidS, flagfield, label) << std::endl; + break; + + + case 5: // 32 bit Identifier field for service label + SId = getBits(d, 16, 32); + offset = 48; + service = findServiceId(SId); + if (service) { + for (int i = 0; i < 16; i ++) { + label[i] = getBits_8(d, offset); + offset += 8; + } + service->serviceLabel.fig1_flag = getBits(d, offset, 16); + service->serviceLabel.fig1_label = label; + service->serviceLabel.setCharset(charSet); + + // MB: Added + myRadioInterface.onSetServiceLabel(SId, service->serviceLabel); + +#ifdef MSC_DATA__ + myRadioInterface.onServiceDetected(SId); +#endif + } + break; + + /* + case 6: // XPAD label + uint8_t XPAD_aid; + pd_flag = getBits(d, 16, 1); + SCidS = getBits(d, 20, 4); + if (pd_flag) { // 32 bits identifier for XPAD label + SId = getBits(d, 24, 32); + XPAD_aid = getBits(d, 59, 5); + offset = 64; + } + else { // 16 bit identifier for XPAD label + SId = getBits(d, 24, 16); + XPAD_aid = getBits(d, 43, 5); + offset = 48; + } + + for (int i = 0; i < 16; i ++) { + label[i] = getBits_8 (d, offset + 8 * i); + } + + // fprintf(stderr, "fib-processor:" + // "FIG1/6: SId = %8x\tp/d = %d\t SCidS = %1X\tXPAD_aid = %2u\t%s\n", + // SId, pd_flag, SCidS, XPAD_aid, label) << std::endl; + break; + */ + + default: + // std::clog << "fib-processor:" << "FIG1/%d: not handled now\n", extension) << std::endl; + break; + } +} + +static void handle_ext_label_data_field(const uint8_t *f, uint8_t len_bytes, + uint8_t toggle_flag, uint8_t segment_index, uint8_t rfu, + DabLabel& label) +{ + if (label.toggle_flag != toggle_flag) { + label.segments.clear(); + label.extended_label_charset = CharacterSet::Undefined; + label.toggle_flag = toggle_flag; + } + + size_t len_character_field = len_bytes; + + if (segment_index == 0) { + // Only if it's the first segment + const uint8_t encoding_flag = (f[0] & 0x80) >> 7; + const uint8_t segment_count = (f[0] & 0x70) >> 4; + label.segment_count = segment_count + 1; + + if (encoding_flag) { + label.extended_label_charset = CharacterSet::UnicodeUcs2; + } + else { + label.extended_label_charset = CharacterSet::UnicodeUtf8; + } + + if (rfu == 0) { + // const uint8_t rfa = (f[0] & 0x0F); + // const uint16_t char_flag = f[1] * 256 + f[2]; + + if (len_bytes <= 3) { + throw std::runtime_error("FIG2 label length too short"); + } + + f += 3; + len_character_field -= 3; + } + else { + // ETSI TS 103 176 draft V2.2.1 (2018-08) gives a new meaning to rfu + // TODO const uint8_t text_control = (f[0] & 0x0F); + + if (len_bytes <= 1) { + throw std::runtime_error("FIG2 label length too short"); + } + + f += 1; + len_character_field -= 1; + } + + label.fig2_rfu = rfu; + } + + std::vector labelbytes(f, f + len_character_field); + label.segments[segment_index] = labelbytes; +} + +// UTF-8 or UCS2 Labels +void FIBProcessor::process_FIG2(uint8_t *d) +{ + // In order to reuse code with etisnoop, convert + // the bit-vector into a byte-vector + std::vector fig_bytes; + for (size_t i = 0; i < 30; i++) { + fig_bytes.push_back(getBits_8(d, 8*i)); + } + + uint8_t *f = fig_bytes.data(); + + const uint8_t figlen = f[0] & 0x1F; + f++; + + const uint8_t toggle_flag = (f[0] & 0x80) >> 7; + const uint8_t segment_index = (f[0] & 0x70) >> 4; + const uint16_t rfu = (f[0] & 0x08) >> 3; + const uint16_t ext = f[0] & 0x07; + + size_t identifier_len; + switch (ext) { + case 0: // Ensemble label + identifier_len = 2; + break; + case 1: // Programme service label + identifier_len = 2; + break; + case 4: // Service component label + { + uint8_t pd = (f[1] & 0x80) >> 7; + identifier_len = (pd == 0) ? 3 : 5; + break; + } + case 5: // Data service label + identifier_len = 4; + break; + default: + return; // Unsupported + } + + const size_t header_length = 1; // FIG data field header + + const uint8_t *figdata = f + header_length + identifier_len; + const size_t data_len_bytes = figlen - header_length - identifier_len; + + // ext is followed by Identifier field of Type 2 field, + // whose length depends on ext + switch (ext) { + case 0: // Ensemble label + { // ETSI EN 300 401 8.1.13 + uint16_t eid = f[1] * 256 + f[2]; + if (figlen <= header_length + identifier_len) { + //std::clog << "FIG2/0 length error " << (int)figlen << std::endl; + } + else if (eid == ensembleId) { + handle_ext_label_data_field(figdata, data_len_bytes, + toggle_flag, segment_index, rfu, ensembleLabel); + } + } + break; + + case 1: // Programme service label + { // ETSI EN 300 401 8.1.14.1 + uint16_t sid = f[1] * 256 + f[2]; + if (figlen <= header_length + identifier_len) { + //std::clog << "FIG2/1 length error " << (int)figlen << std::endl; + } + else { + auto *service = findServiceId(sid); + if (service) { + handle_ext_label_data_field(figdata, data_len_bytes, + toggle_flag, segment_index, rfu, service->serviceLabel); + } + } + } + break; + + case 4: // Service component label + { // ETSI EN 300 401 8.1.14.3 + uint32_t sid; + uint8_t pd = (f[1] & 0x80) >> 7; + uint8_t SCIdS = f[1] & 0x0F; + if (pd == 0) { + sid = f[2] * 256 + \ + f[3]; + } + else { + sid = ((uint32_t)f[2] << 24) | + ((uint32_t)f[3] << 16) | + ((uint32_t)f[4] << 8) | + ((uint32_t)f[5]); + } + if (figlen <= header_length + identifier_len) { + //std::clog << "FIG2/4 length error " << (int)figlen << std::endl; + } + else { + auto *component = findComponent(sid, SCIdS); + if (component) { + handle_ext_label_data_field(figdata, data_len_bytes, + toggle_flag, segment_index, rfu, component->componentLabel); + } + } + } + break; + + case 5: // Data service label + { // ETSI EN 300 401 8.1.14.2 + const uint32_t sid = + ((uint32_t)f[1] << 24) | + ((uint32_t)f[2] << 16) | + ((uint32_t)f[3] << 8) | + ((uint32_t)f[4]); + + if (figlen <= header_length + identifier_len) { + //std::clog << "FIG2/5 length error " << (int)figlen << std::endl; + } + else { + auto *service = findServiceId(sid); + if (service) { + handle_ext_label_data_field(figdata, data_len_bytes, + toggle_flag, segment_index, rfu, service->serviceLabel); + } + } + } + break; + } +} + +// locate a reference to the entry for the Service serviceId +Service *FIBProcessor::findServiceId(uint32_t serviceId) +{ + for (size_t i = 0; i < services.size(); i++) { + if (services[i].serviceId == serviceId) { + return &services[i]; + } + } + + return nullptr; +} + +ServiceComponent *FIBProcessor::findComponent(uint32_t serviceId, int16_t SCIdS) +{ + auto comp = std::find_if(components.begin(), components.end(), + [&](const ServiceComponent& sc) { + return sc.SId == serviceId && sc.componentNr == SCIdS; + }); + + if (comp == components.end()) { + return nullptr; + } + else { + return &(*comp); + } +} + +ServiceComponent *FIBProcessor::findPacketComponent(int16_t SCId) +{ + for (auto& component : components) { + if (component.TMid != 03) { + continue; + } + if (component.SCId == SCId) { + return &component; + } + } + return nullptr; +} + +// bindAudioService is the main processor for - what the name suggests - +// connecting the description of audioservices to a SID +void FIBProcessor::bindAudioService( + int8_t TMid, + uint32_t SId, + int16_t compnr, + int16_t subChId, + int16_t ps_flag, + int16_t ASCTy) +{ + Service *s = findServiceId(SId); + if (!s) return; + + if (std::find_if(components.begin(), components.end(), + [&](const ServiceComponent& sc) { + return sc.SId == s->serviceId && sc.componentNr == compnr; + }) == components.end()) { + ServiceComponent newcomp; + newcomp.TMid = TMid; + newcomp.componentNr = compnr; + newcomp.SId = SId; + newcomp.subchannelId = subChId; + newcomp.PS_flag = ps_flag; + newcomp.ASCTy = ASCTy; + components.push_back(newcomp); + + // std::clog << "fib-processor:" << "service %8x (comp %d) is audio\n", SId, compnr) << std::endl; + } +} + +void FIBProcessor::bindDataStreamService( + int8_t TMid, + uint32_t SId, + int16_t compnr, + int16_t subChId, + int16_t ps_flag, + int16_t DSCTy) +{ + Service *s = findServiceId(SId); + if (!s) return; + + if (std::find_if(components.begin(), components.end(), + [&](const ServiceComponent& sc) { + return sc.SId == s->serviceId && sc.componentNr == compnr; + }) == components.end()) { + ServiceComponent newcomp; + newcomp.TMid = TMid; + newcomp.SId = SId; + newcomp.subchannelId = subChId; + newcomp.componentNr = compnr; + newcomp.PS_flag = ps_flag; + newcomp.DSCTy = DSCTy; + components.push_back(newcomp); + + // std::clog << "fib-processor:" << "service %8x (comp %d) is packet\n", SId, compnr) << std::endl; + } +} + +// bindPacketService is the main processor for - what the name suggests - +// connecting the service component defining the service to the SId, +/// Note that the subchannel is assigned through a FIG0/3 +void FIBProcessor::bindPacketService( + int8_t TMid, + uint32_t SId, + int16_t compnr, + int16_t SCId, + int16_t ps_flag, + int16_t CAflag) +{ + Service *s = findServiceId(SId); + if (!s) return; + + if (std::find_if(components.begin(), components.end(), + [&](const ServiceComponent& sc) { + return sc.SId == s->serviceId && sc.componentNr == compnr; + }) == components.end()) { + ServiceComponent newcomp; + newcomp.TMid = TMid; + newcomp.SId = SId; + newcomp.componentNr = compnr; + newcomp.SCId = SCId; + newcomp.PS_flag = ps_flag; + newcomp.CAflag = CAflag; + components.push_back(newcomp); + + // std::clog << "fib-processor:" << "service %8x (comp %d) is packet\n", SId, compnr) << std::endl; + } +} + +void FIBProcessor::dropService(uint32_t SId) +{ + std::stringstream ss; + ss << "Dropping service " << SId; + + services.erase(std::remove_if(services.begin(), services.end(), + [&](const Service& s) { + return s.serviceId == SId; + } + ), services.end()); + + components.erase(std::remove_if(components.begin(), components.end(), + [&](const ServiceComponent& c) { + const bool drop = c.SId == SId; + if (drop) { + ss << ", comp " << c.componentNr; + } + return drop; + } + ), components.end()); + + // Check for orphaned subchannels + for (auto& sub : subChannels) { + if (sub.subChId == -1) { + continue; + } + + auto c_it = std::find_if(components.begin(), components.end(), + [&](const ServiceComponent& c) { + return c.subchannelId == sub.subChId; + }); + + const bool drop = c_it == components.end(); + if (drop) { + ss << ", subch " << sub.subChId; + sub.subChId = -1; + } + } + + //std::clog << ss.str() << std::endl; +} + +void FIBProcessor::clearEnsemble() +{ + std::lock_guard lock(mutex); + components.clear(); + subChannels.resize(64); + services.clear(); + serviceRepeatCount.clear(); + timeLastServiceDecrement = std::chrono::steady_clock::now(); + timeLastFCT0Frame = std::chrono::system_clock::now(); +} + +std::vector FIBProcessor::getServiceList() const +{ + std::lock_guard lock(mutex); + return services; +} + +Service FIBProcessor::getService(uint32_t sId) const +{ + std::lock_guard lock(mutex); + + auto srv = std::find_if(services.begin(), services.end(), + [&](const Service& s) { + return s.serviceId == sId; + }); + + if (srv != services.end()) { + return *srv; + } + else { + return Service(0); + } +} + +std::list FIBProcessor::getComponents(const Service& s) const +{ + std::list c; + std::lock_guard lock(mutex); + for (const auto& component : components) { + if (component.SId == s.serviceId) { + c.push_back(component); + } + } + + return c; +} + +Subchannel FIBProcessor::getSubchannel(const ServiceComponent& sc) const +{ + std::lock_guard lock(mutex); + return subChannels.at(sc.subchannelId); +} + +uint16_t FIBProcessor::getEnsembleId() const +{ + std::lock_guard lock(mutex); + return ensembleId; +} + +uint8_t FIBProcessor::getEnsembleEcc() const +{ + std::lock_guard lock(mutex); + return ensembleEcc; +} + +DabLabel FIBProcessor::getEnsembleLabel() const +{ + std::lock_guard lock(mutex); + return ensembleLabel; +} + +std::chrono::system_clock::time_point FIBProcessor::getTimeLastFCT0Frame() const +{ + std::lock_guard lock(mutex); + return timeLastFCT0Frame; +} diff --git a/src/dabdsp/fib-processor.h b/src/dabdsp/fib-processor.h index daa37c3..921b01c 100644 --- a/src/dabdsp/fib-processor.h +++ b/src/dabdsp/fib-processor.h @@ -1,138 +1,138 @@ -/* - * Copyright (C) 2020 - * Matthias P. Braendli (matthias.braendli@mpb.li) - * - * Copyright (C) 2013 - * Jan van Katwijk (J.vanKatwijk@gmail.com) - * Lazy Chair Programming - * - * This file is part of the SDR-J (JSDR). - * SDR-J is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * SDR-J 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with SDR-J; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ -#ifndef FIB_PROCESSOR -#define FIB_PROCESSOR - -#include -#include -#include -#include -#include -#include -#include -#include -#include "msc-handler.h" -#include "radio-controller.h" - -class FIBProcessor { - public: - FIBProcessor(RadioControllerInterface& mr); - - // called from the demodulator - void processFIB(uint8_t *p, uint16_t fib); - void clearEnsemble(); - - // Called from the frontend - uint16_t getEnsembleId() const; - uint8_t getEnsembleEcc() const; - DabLabel getEnsembleLabel() const; - std::vector getServiceList() const; - Service getService(uint32_t sId) const; - std::list getComponents(const Service& s) const; - Subchannel getSubchannel(const ServiceComponent& sc) const; - std::chrono::system_clock::time_point getTimeLastFCT0Frame() const; - - private: - RadioControllerInterface& myRadioInterface; - Service *findServiceId(uint32_t serviceId); - ServiceComponent *findComponent(uint32_t serviceId, int16_t SCIdS); - ServiceComponent *findPacketComponent(int16_t SCId); - - void bindAudioService( - int8_t TMid, - uint32_t SId, - int16_t compnr, - int16_t subChId, - int16_t ps_flag, - int16_t ASCTy); - - void bindDataStreamService( - int8_t TMid, - uint32_t SId, - int16_t compnr, - int16_t subChId, - int16_t ps_flag, - int16_t DSCTy); - - void bindPacketService( - int8_t TMid, - uint32_t SId, - int16_t compnr, - int16_t SCId, - int16_t ps_flag, - int16_t CAflag); - - void dropService(uint32_t SId); - - void process_FIG0(uint8_t *); - void process_FIG1(uint8_t *); - void process_FIG2(uint8_t *); - void FIG0Extension0(uint8_t *); - void FIG0Extension1(uint8_t *); - void FIG0Extension2(uint8_t *); - void FIG0Extension3(uint8_t *); - void FIG0Extension5(uint8_t *); - void FIG0Extension8(uint8_t *); - void FIG0Extension9(uint8_t *); - void FIG0Extension10(uint8_t *); - void FIG0Extension13(uint8_t *); - void FIG0Extension14(uint8_t *); - void FIG0Extension16(uint8_t *); - void FIG0Extension17(uint8_t *); - void FIG0Extension18(uint8_t *); - void FIG0Extension19(uint8_t *); - void FIG0Extension21(uint8_t *); - void FIG0Extension22(uint8_t *); - - int16_t HandleFIG0Extension1(uint8_t *d, int16_t offset, uint8_t pd); - - int16_t HandleFIG0Extension2( - uint8_t *d, - int16_t offset, - uint8_t cn, - uint8_t pd); - - int16_t HandleFIG0Extension3(uint8_t *d, int16_t used); - int16_t HandleFIG0Extension5(uint8_t *d, int16_t offset); - int16_t HandleFIG0Extension8(uint8_t *d, int16_t used, uint8_t pdBit); - int16_t HandleFIG0Extension13(uint8_t *d, int16_t used, uint8_t pdBit); - int16_t HandleFIG0Extension22(uint8_t *d, int16_t used); - - bool timeOffsetReceived = false; - dab_date_time_t dateTime = {}; - mutable std::mutex mutex; - uint16_t ensembleId = 0; - uint8_t ensembleEcc = 0; - DabLabel ensembleLabel; - std::vector subChannels; - std::vector components; - std::vector services; - std::unordered_map serviceRepeatCount; - std::chrono::steady_clock::time_point timeLastServiceDecrement; - std::chrono::system_clock::time_point timeLastFCT0Frame; -}; - -#endif - +/* + * Copyright (C) 2020 + * Matthias P. Braendli (matthias.braendli@mpb.li) + * + * Copyright (C) 2013 + * Jan van Katwijk (J.vanKatwijk@gmail.com) + * Lazy Chair Programming + * + * This file is part of the SDR-J (JSDR). + * SDR-J is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * SDR-J 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with SDR-J; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef FIB_PROCESSOR +#define FIB_PROCESSOR + +#include +#include +#include +#include +#include +#include +#include +#include +#include "msc-handler.h" +#include "radio-controller.h" + +class FIBProcessor { + public: + FIBProcessor(RadioControllerInterface& mr); + + // called from the demodulator + void processFIB(uint8_t *p, uint16_t fib); + void clearEnsemble(); + + // Called from the frontend + uint16_t getEnsembleId() const; + uint8_t getEnsembleEcc() const; + DabLabel getEnsembleLabel() const; + std::vector getServiceList() const; + Service getService(uint32_t sId) const; + std::list getComponents(const Service& s) const; + Subchannel getSubchannel(const ServiceComponent& sc) const; + std::chrono::system_clock::time_point getTimeLastFCT0Frame() const; + + private: + RadioControllerInterface& myRadioInterface; + Service *findServiceId(uint32_t serviceId); + ServiceComponent *findComponent(uint32_t serviceId, int16_t SCIdS); + ServiceComponent *findPacketComponent(int16_t SCId); + + void bindAudioService( + int8_t TMid, + uint32_t SId, + int16_t compnr, + int16_t subChId, + int16_t ps_flag, + int16_t ASCTy); + + void bindDataStreamService( + int8_t TMid, + uint32_t SId, + int16_t compnr, + int16_t subChId, + int16_t ps_flag, + int16_t DSCTy); + + void bindPacketService( + int8_t TMid, + uint32_t SId, + int16_t compnr, + int16_t SCId, + int16_t ps_flag, + int16_t CAflag); + + void dropService(uint32_t SId); + + void process_FIG0(uint8_t *); + void process_FIG1(uint8_t *); + void process_FIG2(uint8_t *); + void FIG0Extension0(uint8_t *); + void FIG0Extension1(uint8_t *); + void FIG0Extension2(uint8_t *); + void FIG0Extension3(uint8_t *); + void FIG0Extension5(uint8_t *); + void FIG0Extension8(uint8_t *); + void FIG0Extension9(uint8_t *); + void FIG0Extension10(uint8_t *); + void FIG0Extension13(uint8_t *); + void FIG0Extension14(uint8_t *); + void FIG0Extension16(uint8_t *); + void FIG0Extension17(uint8_t *); + void FIG0Extension18(uint8_t *); + void FIG0Extension19(uint8_t *); + void FIG0Extension21(uint8_t *); + void FIG0Extension22(uint8_t *); + + int16_t HandleFIG0Extension1(uint8_t *d, int16_t offset, uint8_t pd); + + int16_t HandleFIG0Extension2( + uint8_t *d, + int16_t offset, + uint8_t cn, + uint8_t pd); + + int16_t HandleFIG0Extension3(uint8_t *d, int16_t used); + int16_t HandleFIG0Extension5(uint8_t *d, int16_t offset); + int16_t HandleFIG0Extension8(uint8_t *d, int16_t used, uint8_t pdBit); + int16_t HandleFIG0Extension13(uint8_t *d, int16_t used, uint8_t pdBit); + int16_t HandleFIG0Extension22(uint8_t *d, int16_t used); + + bool timeOffsetReceived = false; + dab_date_time_t dateTime = {}; + mutable std::mutex mutex; + uint16_t ensembleId = 0; + uint8_t ensembleEcc = 0; + DabLabel ensembleLabel; + std::vector subChannels; + std::vector components; + std::vector services; + std::unordered_map serviceRepeatCount; + std::chrono::steady_clock::time_point timeLastServiceDecrement; + std::chrono::system_clock::time_point timeLastFCT0Frame; +}; + +#endif + diff --git a/src/dabdsp/fic-handler.cpp b/src/dabdsp/fic-handler.cpp index 26b977c..bca4cb5 100644 --- a/src/dabdsp/fic-handler.cpp +++ b/src/dabdsp/fic-handler.cpp @@ -1,241 +1,241 @@ -/* - * Copyright (C) 2019 - * Matthias P. Braendli (matthias.braendli@mpb.li) - * - * Copyright (C) 2013 - * Jan van Katwijk (J.vanKatwijk@gmail.com) - * Lazy Chair Computing - * - * This file is part of the SDR-J (JSDR). - * SDR-J is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * SDR-J 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with SDR-J; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - -#include "fic-handler.h" -#include "msc-handler.h" -#include "protTables.h" - -// The 3072 bits of the serial motherword shall be split into -// 24 blocks of 128 bits each. -// The first 21 blocks shall be subjected to -// puncturing (per 32 bits) according to PI_16 -// The next three blocks shall be subjected to -// puncturing (per 32 bits) according to PI_15 -// The last 24 bits shall be subjected to puncturing -// according to the table X - -uint8_t PI_X [24] = { - 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, - 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0 -}; - -/** - * \class FicHandler - * We get in - through get_ficBlock - the FIC data - * in units of 768 bits. - * We follow the standard and apply conv coding and - * puncturing. - * The data is sent through to the fic processor - */ -FicHandler::FicHandler(RadioControllerInterface& mr) : - Viterbi(768), - fibProcessor(mr), - myRadioInterface(mr), - bitBuffer_out(768), - ofdm_input(2304), - viterbiBlock(3072 + 24) -{ - PI_15 = getPCodes(15 - 1); - PI_16 = getPCodes(16 - 1); - std::vector shiftRegister(9, 1); - - for (int i = 0; i < 768; i++) { - PRBS[i] = shiftRegister[8] ^ shiftRegister[4]; - for (int j = 8; j > 0; j--) { - shiftRegister[j] = shiftRegister[j - 1]; - } - - shiftRegister[0] = PRBS[i]; - } -} - -/** - * \brief setBitsperBlock - * The number of bits to be processed per incoming block - * is 2 * p -> K, which still depends on the Mode. - * for Mode I it is 2 * 1536, for Mode II, it is 2 * 384, - * for Mode III it is 192, Mode IV gives 2 * 768. - * for Mode II we will get the 2304 bits after having read - * the 3 FIC blocks, - * for Mode IV we will get 3 * 2 * 768 = 4608, i.e. two resulting blocks - * Note that Mode III is NOT supported - */ - -void FicHandler::setBitsperBlock(int16_t b) -{ - if ( (b == 2 * 384) || - (b == 2 * 768) || - (b == 2 * 1536)) { - bitsperBlock = b; - } - index = 0; - ficno = 0; -} - -/** - * \brief processFicBlock - * The number of bits to be processed per incoming block - * is 2 * p -> K, which still depends on the Mode. - * for Mode I it is 2 * 1536, for Mode II, it is 2 * 384, - * for Mode III it is 192, Mode IV gives 2 * 768. - * for Mode II we will get the 2304 bits after having read - * the 3 FIC blocks, each with 768 bits. - * for Mode IV we will get 3 * 2 * 768 = 4608, i.e. two resulting blocks - * Note that Mode III is NOT supported - * - * The function is called with a blkno. This should be 1, 2 or 3 - * for each time 2304 bits are in, we call processFicInput - */ -void FicHandler::processFicBlock(const softbit_t *data, int16_t blkno) -{ - if (blkno == 1) { - index = 0; - ficno = 0; - } - - if ((1 <= blkno) && (blkno <= 3)) { - for (int i = 0; i < bitsperBlock; i ++) { - ofdm_input[index ++] = data[i]; - if (index >= 2304) { - processFicInput(ofdm_input.data(), ficno); - index = 0; - ficno++; - } - } - } - else { - fprintf(stderr, "You should not call ficBlock here\n"); - } - // we are pretty sure now that after block 4, we end up - // with index = 0 -} - -/** - * \brief processFicInput - * we have a vector of 2304 (0 .. 2303) soft bits that has - * to be de-punctured and de-conv-ed into a block of 768 bits - * In this approach we first create the full 3072 block (i.e. - * we first depuncture, and then we apply the deconvolution - * In the next coding step, we will combine this function with the - * one above - */ -void FicHandler::processFicInput(const softbit_t *ficblock, int16_t ficno) -{ - int16_t input_counter = 0; - int16_t i, k; - int32_t local = 0; - - memset(viterbiBlock.data(), 0, viterbiBlock.size() * sizeof(*viterbiBlock.data())); - - /** - * a block of 2304 bits is considered to be a codeword - * In the first step we have 21 blocks with puncturing according to PI_16 - * each 128 bit block contains 4 subblocks of 32 bits - * on which the given puncturing is applied - */ - for (i = 0; i < 21; i ++) { - for (k = 0; k < 32 * 4; k ++) { - if (PI_16 [k % 32] != 0) { - viterbiBlock[local] = ficblock[input_counter ++]; - } - local ++; - } - } - - /** - * In the second step - * we have 3 blocks with puncturing according to PI_15 - * each 128 bit block contains 4 subblocks of 32 bits - * on which the given puncturing is applied - */ - for (i = 0; i < 3; i ++) { - for (k = 0; k < 32 * 4; k ++) { - if (PI_15 [k % 32] != 0) { - viterbiBlock[local] = ficblock[input_counter ++]; - } - local++; - } - } - - /** - * we have a final block of 24 bits with puncturing according to PI_X - * This block constitutes the 6 * 4 bits of the register itself. - */ - for (k = 0; k < 24; k ++) { - if (PI_X [k] != 0) { - viterbiBlock[local] = ficblock[input_counter++]; - } - local ++; - } - - /** - * Now we have the full word ready for deconvolution - * deconvolution is according to DAB standard section 11.2 - */ - deconvolve(viterbiBlock.data(), bitBuffer_out.data()); - - /** - * if everything worked as planned, we now have a - * 768 bit vector containing three FIB's - * - * first step: energy dispersal according to the DAB standard - * We use a predefined vector PRBS - */ - for (i = 0; i < 768; i ++) { - bitBuffer_out[i] ^= PRBS[i]; - } - - /** - * each of the fib blocks is protected by a crc - * (we know that there are three fib blocks each time we are here - * we keep track of the successrate - */ - for (i = ficno * 3; i < ficno * 3 + 3; i ++) { - uint8_t *p = &bitBuffer_out[(i % 3) * 256]; - const bool crcvalid = check_CRC_bits(p, 256); - myRadioInterface.onFIBDecodeSuccess(crcvalid, p); - if (crcvalid) { - fibProcessor.processFIB(p, ficno); - - if (fic_decode_success_ratio < 10) { - fic_decode_success_ratio++; - } - } - else if (fic_decode_success_ratio > 0) { - fic_decode_success_ratio--; - } - } -} - -void FicHandler::clearEnsemble() -{ - fibProcessor.clearEnsemble(); -} - -int FicHandler::getFicDecodeRatioPercent() -{ - return fic_decode_success_ratio * 10; -} - +/* + * Copyright (C) 2019 + * Matthias P. Braendli (matthias.braendli@mpb.li) + * + * Copyright (C) 2013 + * Jan van Katwijk (J.vanKatwijk@gmail.com) + * Lazy Chair Computing + * + * This file is part of the SDR-J (JSDR). + * SDR-J is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * SDR-J 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with SDR-J; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "fic-handler.h" +#include "msc-handler.h" +#include "protTables.h" + +// The 3072 bits of the serial motherword shall be split into +// 24 blocks of 128 bits each. +// The first 21 blocks shall be subjected to +// puncturing (per 32 bits) according to PI_16 +// The next three blocks shall be subjected to +// puncturing (per 32 bits) according to PI_15 +// The last 24 bits shall be subjected to puncturing +// according to the table X + +uint8_t PI_X [24] = { + 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, + 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0 +}; + +/** + * \class FicHandler + * We get in - through get_ficBlock - the FIC data + * in units of 768 bits. + * We follow the standard and apply conv coding and + * puncturing. + * The data is sent through to the fic processor + */ +FicHandler::FicHandler(RadioControllerInterface& mr) : + Viterbi(768), + fibProcessor(mr), + myRadioInterface(mr), + bitBuffer_out(768), + ofdm_input(2304), + viterbiBlock(3072 + 24) +{ + PI_15 = getPCodes(15 - 1); + PI_16 = getPCodes(16 - 1); + std::vector shiftRegister(9, 1); + + for (int i = 0; i < 768; i++) { + PRBS[i] = shiftRegister[8] ^ shiftRegister[4]; + for (int j = 8; j > 0; j--) { + shiftRegister[j] = shiftRegister[j - 1]; + } + + shiftRegister[0] = PRBS[i]; + } +} + +/** + * \brief setBitsperBlock + * The number of bits to be processed per incoming block + * is 2 * p -> K, which still depends on the Mode. + * for Mode I it is 2 * 1536, for Mode II, it is 2 * 384, + * for Mode III it is 192, Mode IV gives 2 * 768. + * for Mode II we will get the 2304 bits after having read + * the 3 FIC blocks, + * for Mode IV we will get 3 * 2 * 768 = 4608, i.e. two resulting blocks + * Note that Mode III is NOT supported + */ + +void FicHandler::setBitsperBlock(int16_t b) +{ + if ( (b == 2 * 384) || + (b == 2 * 768) || + (b == 2 * 1536)) { + bitsperBlock = b; + } + index = 0; + ficno = 0; +} + +/** + * \brief processFicBlock + * The number of bits to be processed per incoming block + * is 2 * p -> K, which still depends on the Mode. + * for Mode I it is 2 * 1536, for Mode II, it is 2 * 384, + * for Mode III it is 192, Mode IV gives 2 * 768. + * for Mode II we will get the 2304 bits after having read + * the 3 FIC blocks, each with 768 bits. + * for Mode IV we will get 3 * 2 * 768 = 4608, i.e. two resulting blocks + * Note that Mode III is NOT supported + * + * The function is called with a blkno. This should be 1, 2 or 3 + * for each time 2304 bits are in, we call processFicInput + */ +void FicHandler::processFicBlock(const softbit_t *data, int16_t blkno) +{ + if (blkno == 1) { + index = 0; + ficno = 0; + } + + if ((1 <= blkno) && (blkno <= 3)) { + for (int i = 0; i < bitsperBlock; i ++) { + ofdm_input[index ++] = data[i]; + if (index >= 2304) { + processFicInput(ofdm_input.data(), ficno); + index = 0; + ficno++; + } + } + } + else { + fprintf(stderr, "You should not call ficBlock here\n"); + } + // we are pretty sure now that after block 4, we end up + // with index = 0 +} + +/** + * \brief processFicInput + * we have a vector of 2304 (0 .. 2303) soft bits that has + * to be de-punctured and de-conv-ed into a block of 768 bits + * In this approach we first create the full 3072 block (i.e. + * we first depuncture, and then we apply the deconvolution + * In the next coding step, we will combine this function with the + * one above + */ +void FicHandler::processFicInput(const softbit_t *ficblock, int16_t ficno) +{ + int16_t input_counter = 0; + int16_t i, k; + int32_t local = 0; + + memset(viterbiBlock.data(), 0, viterbiBlock.size() * sizeof(*viterbiBlock.data())); + + /** + * a block of 2304 bits is considered to be a codeword + * In the first step we have 21 blocks with puncturing according to PI_16 + * each 128 bit block contains 4 subblocks of 32 bits + * on which the given puncturing is applied + */ + for (i = 0; i < 21; i ++) { + for (k = 0; k < 32 * 4; k ++) { + if (PI_16 [k % 32] != 0) { + viterbiBlock[local] = ficblock[input_counter ++]; + } + local ++; + } + } + + /** + * In the second step + * we have 3 blocks with puncturing according to PI_15 + * each 128 bit block contains 4 subblocks of 32 bits + * on which the given puncturing is applied + */ + for (i = 0; i < 3; i ++) { + for (k = 0; k < 32 * 4; k ++) { + if (PI_15 [k % 32] != 0) { + viterbiBlock[local] = ficblock[input_counter ++]; + } + local++; + } + } + + /** + * we have a final block of 24 bits with puncturing according to PI_X + * This block constitutes the 6 * 4 bits of the register itself. + */ + for (k = 0; k < 24; k ++) { + if (PI_X [k] != 0) { + viterbiBlock[local] = ficblock[input_counter++]; + } + local ++; + } + + /** + * Now we have the full word ready for deconvolution + * deconvolution is according to DAB standard section 11.2 + */ + deconvolve(viterbiBlock.data(), bitBuffer_out.data()); + + /** + * if everything worked as planned, we now have a + * 768 bit vector containing three FIB's + * + * first step: energy dispersal according to the DAB standard + * We use a predefined vector PRBS + */ + for (i = 0; i < 768; i ++) { + bitBuffer_out[i] ^= PRBS[i]; + } + + /** + * each of the fib blocks is protected by a crc + * (we know that there are three fib blocks each time we are here + * we keep track of the successrate + */ + for (i = ficno * 3; i < ficno * 3 + 3; i ++) { + uint8_t *p = &bitBuffer_out[(i % 3) * 256]; + const bool crcvalid = check_CRC_bits(p, 256); + myRadioInterface.onFIBDecodeSuccess(crcvalid, p); + if (crcvalid) { + fibProcessor.processFIB(p, ficno); + + if (fic_decode_success_ratio < 10) { + fic_decode_success_ratio++; + } + } + else if (fic_decode_success_ratio > 0) { + fic_decode_success_ratio--; + } + } +} + +void FicHandler::clearEnsemble() +{ + fibProcessor.clearEnsemble(); +} + +int FicHandler::getFicDecodeRatioPercent() +{ + return fic_decode_success_ratio * 10; +} + diff --git a/src/dabdsp/fic-handler.h b/src/dabdsp/fic-handler.h index 82ea7a1..1b0b34c 100644 --- a/src/dabdsp/fic-handler.h +++ b/src/dabdsp/fic-handler.h @@ -1,69 +1,69 @@ -/* - * Copyright (C) 2019 - * Matthias P. Braendli (matthias.braendli@mpb.li) - * - * Copyright (C) 2013 - * Jan van Katwijk (J.vanKatwijk@gmail.com) - * Lazy Chair Programming - * - * This file is part of the SDR-J (JSDR). - * SDR-J is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * SDR-J 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with SDR-J; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ -/* - * FIC data - */ -#ifndef __FIC_HANDLER -#define __FIC_HANDLER - -#include -#include -#include -#include "viterbi.h" -#include "fib-processor.h" -#include "radio-controller.h" - -class FicHandler: public Viterbi -{ - public: - FicHandler(RadioControllerInterface& mr); - void processFicBlock(const softbit_t *data, int16_t blkno); - void setBitsperBlock(int16_t b); - void clearEnsemble(); - int getFicDecodeRatioPercent(); - - FIBProcessor fibProcessor; - - private: - RadioControllerInterface& myRadioInterface; - void processFicInput(const softbit_t *ficblock, int16_t ficno); - const int8_t *PI_15; - const int8_t *PI_16; - std::vector bitBuffer_out; - std::vector ofdm_input; - std::vector viterbiBlock; - int16_t index = 0; - int16_t bitsperBlock = 2 * 1536; - int16_t ficno = 0; - uint8_t PRBS[768]; - - // Saturating up/down-counter in range [0, 10] corresponding - // to the number of FICs with correct CRC - int fic_decode_success_ratio = 0; -}; - -#endif - - +/* + * Copyright (C) 2019 + * Matthias P. Braendli (matthias.braendli@mpb.li) + * + * Copyright (C) 2013 + * Jan van Katwijk (J.vanKatwijk@gmail.com) + * Lazy Chair Programming + * + * This file is part of the SDR-J (JSDR). + * SDR-J is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * SDR-J 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with SDR-J; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* + * FIC data + */ +#ifndef __FIC_HANDLER +#define __FIC_HANDLER + +#include +#include +#include +#include "viterbi.h" +#include "fib-processor.h" +#include "radio-controller.h" + +class FicHandler: public Viterbi +{ + public: + FicHandler(RadioControllerInterface& mr); + void processFicBlock(const softbit_t *data, int16_t blkno); + void setBitsperBlock(int16_t b); + void clearEnsemble(); + int getFicDecodeRatioPercent(); + + FIBProcessor fibProcessor; + + private: + RadioControllerInterface& myRadioInterface; + void processFicInput(const softbit_t *ficblock, int16_t ficno); + const int8_t *PI_15; + const int8_t *PI_16; + std::vector bitBuffer_out; + std::vector ofdm_input; + std::vector viterbiBlock; + int16_t index = 0; + int16_t bitsperBlock = 2 * 1536; + int16_t ficno = 0; + uint8_t PRBS[768]; + + // Saturating up/down-counter in range [0, 10] corresponding + // to the number of FICs with correct CRC + int fic_decode_success_ratio = 0; +}; + +#endif + + diff --git a/src/dabdsp/freq-interleaver.cpp b/src/dabdsp/freq-interleaver.cpp index 1b2723c..9d9698b 100644 --- a/src/dabdsp/freq-interleaver.cpp +++ b/src/dabdsp/freq-interleaver.cpp @@ -1,92 +1,92 @@ -/* - * - * Copyright (C) 2013 - * Jan van Katwijk (J.vanKatwijk@gmail.com) - * Lazy Chair Programming - * - * This file is part of the SDR-J (JSDR). - * SDR-J is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * SDR-J 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with SDR-J; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - -#include -#include -#include "freq-interleaver.h" - -/** - * \brief createMapper - * create the mapping table for the (de-)interleaver - * formulas according to section 14.6 (Frequency interleaving) - * of the DAB standard - */ - -static std::vector createMapper(int16_t T_u, int16_t V1, int16_t lwb, int16_t upb) -{ - std::vector tmp(T_u); - std::vector v(T_u); - int16_t index = 0; - int16_t i; - - tmp[0] = 0; - for (i = 1; i < T_u; i ++) { - tmp[i] = (13 * tmp[i - 1] + V1) % T_u; - } - - for (i = 0; i < T_u; i ++) { - if (tmp[i] == T_u / 2) - continue; - if ((tmp[i] < lwb) || (tmp[i] > upb)) - continue; - // we now have a table with values from lwb .. upb - - v[index ++] = tmp[i] - T_u / 2; - // we now have a table with values from lwb - T_u / 2 .. lwb + T_u / 2 - } - - return v; -} - -FrequencyInterleaver::FrequencyInterleaver(const DABParams& param) -{ - switch (param.dabMode) { - case 1: - default: // shouldn't happen - permTable = createMapper (param.T_u, - 511, 256, 256 + param.K); - break; - case 2: - permTable = createMapper (param.T_u, - 127, 64, 64 + param.K); - break; - - case 3: - permTable = createMapper (param.T_u, - 63, 32, 32 + param.K); - break; - - case 4: - permTable = createMapper (param.T_u, - 255, 128, 128 + param.K); - break; - } -} - -// according to the standard, the map is a function from -// 0 .. 1535->-768 .. 768 (with exclusion of {0}) -int16_t FrequencyInterleaver::mapIn(int16_t n) -{ - return permTable[n]; -} - +/* + * + * Copyright (C) 2013 + * Jan van Katwijk (J.vanKatwijk@gmail.com) + * Lazy Chair Programming + * + * This file is part of the SDR-J (JSDR). + * SDR-J is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * SDR-J 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with SDR-J; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include "freq-interleaver.h" + +/** + * \brief createMapper + * create the mapping table for the (de-)interleaver + * formulas according to section 14.6 (Frequency interleaving) + * of the DAB standard + */ + +static std::vector createMapper(int16_t T_u, int16_t V1, int16_t lwb, int16_t upb) +{ + std::vector tmp(T_u); + std::vector v(T_u); + int16_t index = 0; + int16_t i; + + tmp[0] = 0; + for (i = 1; i < T_u; i ++) { + tmp[i] = (13 * tmp[i - 1] + V1) % T_u; + } + + for (i = 0; i < T_u; i ++) { + if (tmp[i] == T_u / 2) + continue; + if ((tmp[i] < lwb) || (tmp[i] > upb)) + continue; + // we now have a table with values from lwb .. upb + + v[index ++] = tmp[i] - T_u / 2; + // we now have a table with values from lwb - T_u / 2 .. lwb + T_u / 2 + } + + return v; +} + +FrequencyInterleaver::FrequencyInterleaver(const DABParams& param) +{ + switch (param.dabMode) { + case 1: + default: // shouldn't happen + permTable = createMapper (param.T_u, + 511, 256, 256 + param.K); + break; + case 2: + permTable = createMapper (param.T_u, + 127, 64, 64 + param.K); + break; + + case 3: + permTable = createMapper (param.T_u, + 63, 32, 32 + param.K); + break; + + case 4: + permTable = createMapper (param.T_u, + 255, 128, 128 + param.K); + break; + } +} + +// according to the standard, the map is a function from +// 0 .. 1535->-768 .. 768 (with exclusion of {0}) +int16_t FrequencyInterleaver::mapIn(int16_t n) +{ + return permTable[n]; +} + diff --git a/src/dabdsp/freq-interleaver.h b/src/dabdsp/freq-interleaver.h index 8b21188..c888a88 100644 --- a/src/dabdsp/freq-interleaver.h +++ b/src/dabdsp/freq-interleaver.h @@ -1,48 +1,48 @@ -/* - * Copyright (C) 2018 - * Matthias P. Braendli (matthias.braendli@mpb.li) - * - * Copyright (C) 2013 - * Jan van Katwijk (J.vanKatwijk@gmail.com) - * Lazy Chair Programming - * - * This file is part of the SDR-J (JSDR). - * SDR-J is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * SDR-J 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with SDR-J; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - -#ifndef __FREQ_INTERLEAVER__ -#define __FREQ_INTERLEAVER__ -#include -#include -#include "dab-constants.h" - -/** - * \class FrequencyInterleaver - * Implements frequency interleaving according to section 14.6 - * of the DAB standard - */ -class FrequencyInterleaver -{ - public: - FrequencyInterleaver(const DABParams& param); - int16_t mapIn(int16_t); - - private: - std::vector permTable; -}; - -#endif - +/* + * Copyright (C) 2018 + * Matthias P. Braendli (matthias.braendli@mpb.li) + * + * Copyright (C) 2013 + * Jan van Katwijk (J.vanKatwijk@gmail.com) + * Lazy Chair Programming + * + * This file is part of the SDR-J (JSDR). + * SDR-J is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * SDR-J 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with SDR-J; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef __FREQ_INTERLEAVER__ +#define __FREQ_INTERLEAVER__ +#include +#include +#include "dab-constants.h" + +/** + * \class FrequencyInterleaver + * Implements frequency interleaving according to section 14.6 + * of the DAB standard + */ +class FrequencyInterleaver +{ + public: + FrequencyInterleaver(const DABParams& param); + int16_t mapIn(int16_t); + + private: + std::vector permTable; +}; + +#endif + diff --git a/src/dabdsp/init_rs.h b/src/dabdsp/init_rs.h index 6c7b9c3..a303a57 100644 --- a/src/dabdsp/init_rs.h +++ b/src/dabdsp/init_rs.h @@ -1,104 +1,104 @@ -/* Common code for intializing a Reed-Solomon control block (char or int symbols) - * Copyright 2004 Phil Karn, KA9Q - * May be used under the terms of the GNU Lesser General Public License (LGPL) - */ - -{ - int i, j, sr,root,iprim; - - rs = NULL; - /* Check parameter ranges */ - if(symsize < 0 || (size_t)symsize > 8*sizeof(data_t)){ - goto done; - } - - if(fcr < 0 || fcr >= (1<= (1<= (1<= ((1<mm = symsize; - rs->nn = (1<pad = pad; - - rs->alpha_to = (data_t *)malloc(sizeof(data_t)*(rs->nn+1)); - if(rs->alpha_to == NULL){ - free(rs); - rs = NULL; - goto done; - } - rs->index_of = (data_t *)malloc(sizeof(data_t)*(rs->nn+1)); - if(rs->index_of == NULL){ - free(rs->alpha_to); - free(rs); - rs = NULL; - goto done; - } - - /* Generate Galois field lookup tables */ - rs->index_of[0] = A0; /* log(zero) = -inf */ - rs->alpha_to[A0] = 0; /* alpha**-inf = 0 */ - sr = 1; - for(i=0;inn;i++){ - rs->index_of[sr] = i; - rs->alpha_to[i] = sr; - sr <<= 1; - if(sr & (1<nn; - } - if(sr != 1){ - /* field generator polynomial is not primitive! */ - free(rs->alpha_to); - free(rs->index_of); - free(rs); - rs = NULL; - goto done; - } - - /* Form RS code generator polynomial from its roots */ - rs->genpoly = (data_t *)malloc(sizeof(data_t)*(nroots+1)); - if(rs->genpoly == NULL){ - free(rs->alpha_to); - free(rs->index_of); - free(rs); - rs = NULL; - goto done; - } - rs->fcr = fcr; - rs->prim = prim; - rs->nroots = nroots; - - /* Find prim-th root of 1, used in decoding */ - for(iprim=1;(iprim % prim) != 0;iprim += rs->nn) - ; - rs->iprim = iprim / prim; - - rs->genpoly[0] = 1; - for (i = 0,root=fcr*prim; i < nroots; i++,root += prim) { - rs->genpoly[i+1] = 1; - - /* Multiply rs->genpoly[] by @**(root + x) */ - for (j = i; j > 0; j--){ - if (rs->genpoly[j] != 0) - rs->genpoly[j] = rs->genpoly[j-1] ^ rs->alpha_to[modnn(rs,rs->index_of[rs->genpoly[j]] + root)]; - else - rs->genpoly[j] = rs->genpoly[j-1]; - } - /* rs->genpoly[0] can never be zero */ - rs->genpoly[0] = rs->alpha_to[modnn(rs,rs->index_of[rs->genpoly[0]] + root)]; - } - /* convert rs->genpoly[] to index form for quicker encoding */ - for (i = 0; i <= nroots; i++) - rs->genpoly[i] = rs->index_of[rs->genpoly[i]]; - done:; - -} +/* Common code for intializing a Reed-Solomon control block (char or int symbols) + * Copyright 2004 Phil Karn, KA9Q + * May be used under the terms of the GNU Lesser General Public License (LGPL) + */ + +{ + int i, j, sr,root,iprim; + + rs = NULL; + /* Check parameter ranges */ + if(symsize < 0 || (size_t)symsize > 8*sizeof(data_t)){ + goto done; + } + + if(fcr < 0 || fcr >= (1<= (1<= (1<= ((1<mm = symsize; + rs->nn = (1<pad = pad; + + rs->alpha_to = (data_t *)malloc(sizeof(data_t)*(rs->nn+1)); + if(rs->alpha_to == NULL){ + free(rs); + rs = NULL; + goto done; + } + rs->index_of = (data_t *)malloc(sizeof(data_t)*(rs->nn+1)); + if(rs->index_of == NULL){ + free(rs->alpha_to); + free(rs); + rs = NULL; + goto done; + } + + /* Generate Galois field lookup tables */ + rs->index_of[0] = A0; /* log(zero) = -inf */ + rs->alpha_to[A0] = 0; /* alpha**-inf = 0 */ + sr = 1; + for(i=0;inn;i++){ + rs->index_of[sr] = i; + rs->alpha_to[i] = sr; + sr <<= 1; + if(sr & (1<nn; + } + if(sr != 1){ + /* field generator polynomial is not primitive! */ + free(rs->alpha_to); + free(rs->index_of); + free(rs); + rs = NULL; + goto done; + } + + /* Form RS code generator polynomial from its roots */ + rs->genpoly = (data_t *)malloc(sizeof(data_t)*(nroots+1)); + if(rs->genpoly == NULL){ + free(rs->alpha_to); + free(rs->index_of); + free(rs); + rs = NULL; + goto done; + } + rs->fcr = fcr; + rs->prim = prim; + rs->nroots = nroots; + + /* Find prim-th root of 1, used in decoding */ + for(iprim=1;(iprim % prim) != 0;iprim += rs->nn) + ; + rs->iprim = iprim / prim; + + rs->genpoly[0] = 1; + for (i = 0,root=fcr*prim; i < nroots; i++,root += prim) { + rs->genpoly[i+1] = 1; + + /* Multiply rs->genpoly[] by @**(root + x) */ + for (j = i; j > 0; j--){ + if (rs->genpoly[j] != 0) + rs->genpoly[j] = rs->genpoly[j-1] ^ rs->alpha_to[modnn(rs,rs->index_of[rs->genpoly[j]] + root)]; + else + rs->genpoly[j] = rs->genpoly[j-1]; + } + /* rs->genpoly[0] can never be zero */ + rs->genpoly[0] = rs->alpha_to[modnn(rs,rs->index_of[rs->genpoly[0]] + root)]; + } + /* convert rs->genpoly[] to index form for quicker encoding */ + for (i = 0; i <= nroots; i++) + rs->genpoly[i] = rs->index_of[rs->genpoly[i]]; + done:; + +} diff --git a/src/dabdsp/init_rs_char.c b/src/dabdsp/init_rs_char.c index 01117bf..707e1eb 100644 --- a/src/dabdsp/init_rs_char.c +++ b/src/dabdsp/init_rs_char.c @@ -1,35 +1,35 @@ -/* Initialize a RS codec - * - * Copyright 2002 Phil Karn, KA9Q - * May be used under the terms of the GNU Lesser General Public License (LGPL) - */ -#include - -#include "char.h" -#include "rs-common.h" - -void free_rs_dab(void *p){ - struct rs *rs = (struct rs *)p; - - free(rs->alpha_to); - free(rs->index_of); - free(rs->genpoly); - free(rs); -} - -/* Initialize a Reed-Solomon codec - * symsize = symbol size, bits - * gfpoly = Field generator polynomial coefficients - * fcr = first root of RS code generator polynomial, index form - * prim = primitive element to generate polynomial roots - * nroots = RS code generator polynomial degree (number of roots) - * pad = padding bytes at front of shortened block - */ -void *init_rs_dab(int symsize,int gfpoly,int fcr,int prim, - int nroots,int pad){ - struct rs *rs; - -#include "init_rs.h" - - return rs; -} +/* Initialize a RS codec + * + * Copyright 2002 Phil Karn, KA9Q + * May be used under the terms of the GNU Lesser General Public License (LGPL) + */ +#include + +#include "char.h" +#include "rs-common.h" + +void free_rs_dab(void *p){ + struct rs *rs = (struct rs *)p; + + free(rs->alpha_to); + free(rs->index_of); + free(rs->genpoly); + free(rs); +} + +/* Initialize a Reed-Solomon codec + * symsize = symbol size, bits + * gfpoly = Field generator polynomial coefficients + * fcr = first root of RS code generator polynomial, index form + * prim = primitive element to generate polynomial roots + * nroots = RS code generator polynomial degree (number of roots) + * pad = padding bytes at front of shortened block + */ +void *init_rs_dab(int symsize,int gfpoly,int fcr,int prim, + int nroots,int pad){ + struct rs *rs; + +#include "init_rs.h" + + return rs; +} diff --git a/src/dabdsp/msc-handler.cpp b/src/dabdsp/msc-handler.cpp index 13795d9..4655036 100644 --- a/src/dabdsp/msc-handler.cpp +++ b/src/dabdsp/msc-handler.cpp @@ -1,166 +1,166 @@ -/* - * Copyright (C) 2018 - * Matthias P. Braendli (matthias.braendli@mpb.li) - * - * Copyright (C) 2013 - * Jan van Katwijk (J.vanKatwijk@gmail.com) - * Lazy Chair Programming - * - * This file is part of the SDR-J (JSDR). - * SDR-J is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * SDR-J 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with SDR-J; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ -#include -#include "dab-constants.h" -#include "msc-handler.h" -#include "dab-virtual.h" -#include "dab-audio.h" - -// Interface program for processing the MSC. -// Merely a dispatcher for the selected service -// -// The ofdm processor assumes the existence of an msc-handler, whether -// a service is selected or not. - -#define CUSize (4 * 16) -// Note CIF counts from 0 .. 3 -MscHandler::MscHandler( - const DABParams& p, - bool show_crcErrors) : - bitsperBlock(2 * p.K), - show_crcErrors(show_crcErrors), - cifVector(864 * CUSize) -{ - if (p.dabMode == 4) { // 2 CIFS per 76 blocks - numberofblocksperCIF = 36; - } - else { - if (p.dabMode == 1) { // 4 CIFS per 76 blocks - numberofblocksperCIF = 18; - } - else { - if (p.dabMode == 2) // 1 CIF per 76 blocks - numberofblocksperCIF = 72; - else // shouldnot/cannot happen - numberofblocksperCIF = 18; - } - } -} - -bool MscHandler::addSubchannel( - ProgrammeHandlerInterface& handler, - AudioServiceComponentType ascty, - const std::string& dumpFileName, - const Subchannel& sub) -{ - std::lock_guard lock(mutex); - - // check not already in list - for (const auto& stream : streams) { - if (stream.subCh.subChId == sub.subChId) { - return true; - } - } - - SelectedStream s(handler, ascty, dumpFileName, sub); - - s.dabHandler = std::make_shared( - ascty, - sub.length * CUSize, - sub.bitrate(), - sub.protectionSettings, - handler, - dumpFileName); - - /* TODO dealing with data - s.dabHandler = std::make_shared(radioInterface, - new_DSCTy, - new_packetAddress, - subChannel.length * CUSize, - subChannel.bitrate(), - subChannel.shortForm, - subChannel.protLevel, - new_DGflag, - new_FEC_scheme, - show_crcErrors); - */ - - streams.push_back(std::move(s)); - - work_to_be_done = true; - return true; -} - -bool MscHandler::removeSubchannel(const Subchannel& sub) -{ - std::lock_guard lock(mutex); - - auto it = std::find_if(streams.begin(), streams.end(), - [&](const SelectedStream& stream) { - return stream.subCh.subChId == sub.subChId; - } ); - - if (it != streams.end()) { - streams.erase(it); - return true; - } - - return false; -} - -// add blocks. First is (should be) block 5, last is (should be) 76 -// Note that this method is called from within the ofdm-processor thread -// while the set_xxx methods are called from within the -// gui thread -// -// Any change in the selected service will only be active -// during te next processMscBlock call. -void MscHandler::processMscBlock(const softbit_t *fbits, int16_t blkno) -{ - std::lock_guard lock(mutex); - - if (!work_to_be_done) - return; - - int16_t currentblk = (blkno - 4) % numberofblocksperCIF; - - // and the normal operation is: - memcpy(&cifVector[currentblk * bitsperBlock], fbits, bitsperBlock * sizeof(softbit_t)); - - if (currentblk < numberofblocksperCIF - 1) - return; - - // OK, now we have a full CIF - blkCount = 0; - cifCount = (cifCount + 1) & 03; - - for (auto& stream : streams) { - softbit_t *myBegin = &cifVector[stream.subCh.startAddr * CUSize]; - - if (stream.dabHandler) { - (void)stream.dabHandler->process(myBegin, stream.subCh.length * CUSize); - } - else { - throw std::logic_error("No dabHandler!"); - } - } -} - -void MscHandler::stopProcessing() -{ - std::lock_guard lock(mutex); - work_to_be_done = false; - streams.clear(); -} - +/* + * Copyright (C) 2018 + * Matthias P. Braendli (matthias.braendli@mpb.li) + * + * Copyright (C) 2013 + * Jan van Katwijk (J.vanKatwijk@gmail.com) + * Lazy Chair Programming + * + * This file is part of the SDR-J (JSDR). + * SDR-J is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * SDR-J 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with SDR-J; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include "dab-constants.h" +#include "msc-handler.h" +#include "dab-virtual.h" +#include "dab-audio.h" + +// Interface program for processing the MSC. +// Merely a dispatcher for the selected service +// +// The ofdm processor assumes the existence of an msc-handler, whether +// a service is selected or not. + +#define CUSize (4 * 16) +// Note CIF counts from 0 .. 3 +MscHandler::MscHandler( + const DABParams& p, + bool show_crcErrors) : + bitsperBlock(2 * p.K), + show_crcErrors(show_crcErrors), + cifVector(864 * CUSize) +{ + if (p.dabMode == 4) { // 2 CIFS per 76 blocks + numberofblocksperCIF = 36; + } + else { + if (p.dabMode == 1) { // 4 CIFS per 76 blocks + numberofblocksperCIF = 18; + } + else { + if (p.dabMode == 2) // 1 CIF per 76 blocks + numberofblocksperCIF = 72; + else // shouldnot/cannot happen + numberofblocksperCIF = 18; + } + } +} + +bool MscHandler::addSubchannel( + ProgrammeHandlerInterface& handler, + AudioServiceComponentType ascty, + const std::string& dumpFileName, + const Subchannel& sub) +{ + std::lock_guard lock(mutex); + + // check not already in list + for (const auto& stream : streams) { + if (stream.subCh.subChId == sub.subChId) { + return true; + } + } + + SelectedStream s(handler, ascty, dumpFileName, sub); + + s.dabHandler = std::make_shared( + ascty, + sub.length * CUSize, + sub.bitrate(), + sub.protectionSettings, + handler, + dumpFileName); + + /* TODO dealing with data + s.dabHandler = std::make_shared(radioInterface, + new_DSCTy, + new_packetAddress, + subChannel.length * CUSize, + subChannel.bitrate(), + subChannel.shortForm, + subChannel.protLevel, + new_DGflag, + new_FEC_scheme, + show_crcErrors); + */ + + streams.push_back(std::move(s)); + + work_to_be_done = true; + return true; +} + +bool MscHandler::removeSubchannel(const Subchannel& sub) +{ + std::lock_guard lock(mutex); + + auto it = std::find_if(streams.begin(), streams.end(), + [&](const SelectedStream& stream) { + return stream.subCh.subChId == sub.subChId; + } ); + + if (it != streams.end()) { + streams.erase(it); + return true; + } + + return false; +} + +// add blocks. First is (should be) block 5, last is (should be) 76 +// Note that this method is called from within the ofdm-processor thread +// while the set_xxx methods are called from within the +// gui thread +// +// Any change in the selected service will only be active +// during te next processMscBlock call. +void MscHandler::processMscBlock(const softbit_t *fbits, int16_t blkno) +{ + std::lock_guard lock(mutex); + + if (!work_to_be_done) + return; + + int16_t currentblk = (blkno - 4) % numberofblocksperCIF; + + // and the normal operation is: + memcpy(&cifVector[currentblk * bitsperBlock], fbits, bitsperBlock * sizeof(softbit_t)); + + if (currentblk < numberofblocksperCIF - 1) + return; + + // OK, now we have a full CIF + blkCount = 0; + cifCount = (cifCount + 1) & 03; + + for (auto& stream : streams) { + softbit_t *myBegin = &cifVector[stream.subCh.startAddr * CUSize]; + + if (stream.dabHandler) { + (void)stream.dabHandler->process(myBegin, stream.subCh.length * CUSize); + } + else { + throw std::logic_error("No dabHandler!"); + } + } +} + +void MscHandler::stopProcessing() +{ + std::lock_guard lock(mutex); + work_to_be_done = false; + streams.clear(); +} + diff --git a/src/dabdsp/msc-handler.h b/src/dabdsp/msc-handler.h index 70d346d..8fa0ed0 100644 --- a/src/dabdsp/msc-handler.h +++ b/src/dabdsp/msc-handler.h @@ -1,100 +1,100 @@ -/* - * Copyright (C) 2018 - * Matthias P. Braendli (matthias.braendli@mpb.li) - * - * Copyright (C) 2013 - * Jan van Katwijk (J.vanKatwijk@gmail.com) - * Lazy Chair Programming - * - * This file is part of the SDR-J (JSDR). - * SDR-J is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * SDR-J 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with SDR-J; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - -/* - * MSC data - */ - -#ifndef MSC_HANDLER -#define MSC_HANDLER - -#include -#include -#include -#include -#include -#include -#include -#include "dab-constants.h" -#include "ringbuffer.h" -#include "radio-controller.h" - -class DabVirtual; - -class MscHandler -{ - public: - MscHandler(const DABParams& p, bool show_crcErrors); - - // Stop processing and remove all subchannels - void stopProcessing(void); - - bool addSubchannel( - ProgrammeHandlerInterface& handler, - AudioServiceComponentType ascty, - const std::string& dumpFileName, - const Subchannel& sub); - - bool removeSubchannel(const Subchannel& sub); - - private: - friend class OfdmDecoder; - void processMscBlock(const softbit_t *fbits, int16_t blkno); - - struct SelectedStream { - SelectedStream( - ProgrammeHandlerInterface& handler, - AudioServiceComponentType ascty, - const std::string& dumpFileName, - const Subchannel& subCh) : - handler(handler), - audioType(ascty), - dumpFileName(dumpFileName), - subCh(subCh) {} - - ProgrammeHandlerInterface& handler; - - AudioServiceComponentType audioType; - const std::string dumpFileName; - const Subchannel subCh; - - std::shared_ptr dabHandler; - }; - - std::mutex mutex; - std::list streams; - - const int16_t bitsperBlock; - int16_t numberofblocksperCIF; - bool show_crcErrors; - - std::vector cifVector; - int16_t cifCount = 0; // msc blocks in CIF - int16_t blkCount = 0; - bool work_to_be_done = false; -}; - -#endif - +/* + * Copyright (C) 2018 + * Matthias P. Braendli (matthias.braendli@mpb.li) + * + * Copyright (C) 2013 + * Jan van Katwijk (J.vanKatwijk@gmail.com) + * Lazy Chair Programming + * + * This file is part of the SDR-J (JSDR). + * SDR-J is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * SDR-J 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with SDR-J; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + * MSC data + */ + +#ifndef MSC_HANDLER +#define MSC_HANDLER + +#include +#include +#include +#include +#include +#include +#include +#include "dab-constants.h" +#include "ringbuffer.h" +#include "radio-controller.h" + +class DabVirtual; + +class MscHandler +{ + public: + MscHandler(const DABParams& p, bool show_crcErrors); + + // Stop processing and remove all subchannels + void stopProcessing(void); + + bool addSubchannel( + ProgrammeHandlerInterface& handler, + AudioServiceComponentType ascty, + const std::string& dumpFileName, + const Subchannel& sub); + + bool removeSubchannel(const Subchannel& sub); + + private: + friend class OfdmDecoder; + void processMscBlock(const softbit_t *fbits, int16_t blkno); + + struct SelectedStream { + SelectedStream( + ProgrammeHandlerInterface& handler, + AudioServiceComponentType ascty, + const std::string& dumpFileName, + const Subchannel& subCh) : + handler(handler), + audioType(ascty), + dumpFileName(dumpFileName), + subCh(subCh) {} + + ProgrammeHandlerInterface& handler; + + AudioServiceComponentType audioType; + const std::string dumpFileName; + const Subchannel subCh; + + std::shared_ptr dabHandler; + }; + + std::mutex mutex; + std::list streams; + + const int16_t bitsperBlock; + int16_t numberofblocksperCIF; + bool show_crcErrors; + + std::vector cifVector; + int16_t cifCount = 0; // msc blocks in CIF + int16_t blkCount = 0; + bool work_to_be_done = false; +}; + +#endif + diff --git a/src/dabdsp/ofdm-decoder.cpp b/src/dabdsp/ofdm-decoder.cpp index 16b9c17..3dabef0 100644 --- a/src/dabdsp/ofdm-decoder.cpp +++ b/src/dabdsp/ofdm-decoder.cpp @@ -1,286 +1,286 @@ -/* - * Copyright (C) 2018 - * Matthias P. Braendli (matthias.braendli@mpb.li) - * - * Copyright (C) 2013 2015 - * Jan van Katwijk (J.vanKatwijk@gmail.com) - * Lazy Chair Programming - * - * This file is part of the SDR-J (JSDR). - * SDR-J is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * SDR-J 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with SDR-J; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - * Once the bits are "in", interpretation and manipulation - * should reconstruct the data symbols. - * Ofdm_decoder is called once every Ts samples, and - * its invocation results in 2 * Tu bits - */ - -#include -#include "ofdm-decoder.h" -#include "profiling.h" -#include - -/** - * \brief OfdmDecoder - * The class OfdmDecoder is - when implemented in a separate thread - - * taking the data from the ofdmProcessor class in, and - * will extract the Tu samples, do an FFT and extract the - * carriers and map them on (soft) bits - */ -OfdmDecoder::OfdmDecoder( - const DABParams& p, - RadioControllerInterface& mr, - FicHandler& ficHandler, - MscHandler& mscHandler) : - params(p), - radioInterface(mr), - ficHandler(ficHandler), - mscHandler(mscHandler), - pending_symbols(params.L), - phaseReference(params.T_u), - fft_handler(p.T_u), - interleaver(p), - ibits(2 * params.K) -{ - T_g = params.T_s - params.T_u; - fft_buffer = fft_handler.getVector(); - - /** - * When implemented in a thread, the thread controls the - * reading in of the data and processing the data through - * functions for handling symbol 0, FIC symbols and MSC symbols. - */ - thread = std::thread(&OfdmDecoder::workerthread, this); -} - -OfdmDecoder::~OfdmDecoder() -{ - running = false; - pending_symbols_cv.notify_all(); - if (thread.joinable()) { - thread.join(); - } -} - -void OfdmDecoder::reset() -{ - running = false; - pending_symbols_cv.notify_all(); - if (thread.joinable()) { - thread.join(); - } - - thread = std::thread(&OfdmDecoder::workerthread, this); -} - -/** - * The code in the thread executes a simple loop, - * waiting for the next symbols and executing the interpretation - * operation for that symbols. - */ -void OfdmDecoder::workerthread() -{ - int currentSym = 0; - - running = true; - - while (running) { - std::unique_lock lock(mutex); - pending_symbols_cv.wait_for(lock, std::chrono::milliseconds(100)); - - if (currentSym == 0) { - constellationPoints.clear(); - constellationPoints.reserve( - (params.L-1) * params.K / constellationDecimation); - } - - while (num_pending_symbols > 0 && running) { - - if (currentSym == 0) - processPRS(); - else - decodeDataSymbol(currentSym); - - currentSym = (currentSym + 1) % (params.L); - num_pending_symbols -= 1; - - if (currentSym == 0) { - radioInterface.onConstellationPoints( - std::move(constellationPoints)); - constellationPoints.clear(); - constellationPoints.reserve( - (params.L-1) * params.K / constellationDecimation); - } - } - } - - //std::clog << "OFDM-decoder:" << "closing down now" << std::endl; -} - -void OfdmDecoder::pushAllSymbols(std::vector >&& syms) -{ - std::unique_lock lock(mutex); - - pending_symbols = std::move(syms); - num_pending_symbols = pending_symbols.size(); - pending_symbols_cv.notify_one(); -} - -/** - * handle symbol 0 as collected from the buffer - */ -void OfdmDecoder::processPRS() -{ - PROFILE(ProcessPRS); - memcpy (fft_buffer, - pending_symbols[0].data(), - params.T_u * sizeof(DSPCOMPLEX)); - fft_handler.do_FFT (); - /** - * The SNR is determined by looking at a segment of bins - * within the signal region and bits outside. - * It is just an indication - */ - snr = 0.7 * snr + 0.3 * get_snr(fft_buffer, 1); - if (++snrCount > 10) { - radioInterface.onSNR(snr); - snrCount = 0; - } - /** - * we are now in the frequency domain, and we keep the carriers - * as coming from the FFT as phase reference. - */ - memcpy(phaseReference.data(), fft_buffer, params.T_u * sizeof (DSPCOMPLEX)); -} - -/** - * For the other symbols, the first step is to go from - * time to frequency domain, to get the carriers. - * - * \brief decodeDataSymbol - * do the transforms and hand over the result to the fichandler or mschandler - */ -void OfdmDecoder::decodeDataSymbol(int32_t sym_ix) -{ - PROFILE(ProcessSymbol); - memcpy (fft_buffer, - pending_symbols[sym_ix].data() + T_g, - params.T_u * sizeof (DSPCOMPLEX)); - //fftlabel: - /** - * first step: do the FFT - */ - fft_handler.do_FFT(); - - /** - * a little optimization: we do not interchange the - * positive/negative frequencies to their right positions. - * The de-interleaving understands this - */ - - PROFILE(Deinterleaver); - /** - * Note that from here on, we are only interested in the - * K useful carriers of the FFT output - */ - for (int16_t i = 0; i < params.K; i ++) { - int16_t index = interleaver.mapIn(i); - if (index < 0) - index += params.T_u; - /** - * decoding is computing the phase difference between - * carriers with the same index in subsequent symbols. - * The carrier of a symbols is the reference for the carrier - * on the same position in the next symbols - */ - const DSPCOMPLEX r1 = fft_buffer[index] * conj (phaseReference[index]); - phaseReference[index] = fft_buffer[index]; - const DSPFLOAT ab1 = 127.0f / l1_norm(r1); - /// split the real and the imaginary part and scale it - - ibits[i] = -real (r1) * ab1; - ibits[params.K + i] = -imag (r1) * ab1; - - if (i % constellationDecimation == 0) { - constellationPoints.push_back(r1); - } - } - - if (sym_ix < 4) { - PROFILE(FICHandler); - ficHandler.processFicBlock(ibits.data(), sym_ix); - } - else { - PROFILE(MSCHandler); - mscHandler.processMscBlock(ibits.data(), sym_ix); - } - PROFILE(SymbolProcessed); -} - -/** - * for the snr we have a full T_u wide vector, with in the middle - * K carriers. - * Just get the strength from the selected carriers compared - * to the strength of the carriers outside that region - * method: 0 Jans method. This method are originally developed by Jan and is not working if neighbor channels are used because it uses occupied bins for the noise calculation - * 1 New method. This method is working also if neighbor channels are used - */ -int16_t OfdmDecoder::get_snr(DSPCOMPLEX *v, uint8_t method) -{ - int16_t i; - DSPFLOAT noise = 0; - DSPFLOAT signal = 0; - const auto T_u = params.T_u; - const auto K = params.K; - int16_t low = T_u / 2 - K / 2; - int16_t high = low + K; - - if(method) - { - for (i = 70; i < low - 20; i ++) // low - 90 samples - noise += abs (v[(T_u / 2 + i) % T_u]); - - for (i = high + 20; i < high + 120; i ++) // 100 samples - noise += abs (v[(T_u / 2 + i) % T_u]); - - noise /= (low - 90 + 100); - for (i = T_u / 2 - K / 4; i < T_u / 2 + K / 4; i ++) - signal += abs (v[(T_u / 2 + i) % T_u]); - - const auto dB_signal_new = get_db_over_256(signal / (K / 2)); - const auto dB_noise_new = get_db_over_256(noise); - const auto snr_new = dB_signal_new - dB_noise_new; - return snr_new; - } - else - { - noise = 0; - signal = 0; - for (i = 10; i < low - 20; i ++) - noise += abs (v[(T_u / 2 + i) % T_u]); - - for (i = high + 20; i < T_u - 10; i ++) - noise += abs (v[(T_u / 2 + i) % T_u]); - - noise /= (low - 30 + T_u - high - 30); - for (i = T_u / 2 - K / 4; i < T_u / 2 + K / 4; i ++) - signal += abs (v[(T_u / 2 + i) % T_u]); - - const auto dB_signal_old = get_db_over_256(signal / (K / 2)); - const auto dB_noise_old = get_db_over_256(noise); - const auto snr_old = dB_signal_old - dB_noise_old; - return snr_old; - } -} +/* + * Copyright (C) 2018 + * Matthias P. Braendli (matthias.braendli@mpb.li) + * + * Copyright (C) 2013 2015 + * Jan van Katwijk (J.vanKatwijk@gmail.com) + * Lazy Chair Programming + * + * This file is part of the SDR-J (JSDR). + * SDR-J is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * SDR-J 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with SDR-J; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Once the bits are "in", interpretation and manipulation + * should reconstruct the data symbols. + * Ofdm_decoder is called once every Ts samples, and + * its invocation results in 2 * Tu bits + */ + +#include +#include "ofdm-decoder.h" +#include "profiling.h" +#include + +/** + * \brief OfdmDecoder + * The class OfdmDecoder is - when implemented in a separate thread - + * taking the data from the ofdmProcessor class in, and + * will extract the Tu samples, do an FFT and extract the + * carriers and map them on (soft) bits + */ +OfdmDecoder::OfdmDecoder( + const DABParams& p, + RadioControllerInterface& mr, + FicHandler& ficHandler, + MscHandler& mscHandler) : + params(p), + radioInterface(mr), + ficHandler(ficHandler), + mscHandler(mscHandler), + pending_symbols(params.L), + phaseReference(params.T_u), + fft_handler(p.T_u), + interleaver(p), + ibits(2 * params.K) +{ + T_g = params.T_s - params.T_u; + fft_buffer = fft_handler.getVector(); + + /** + * When implemented in a thread, the thread controls the + * reading in of the data and processing the data through + * functions for handling symbol 0, FIC symbols and MSC symbols. + */ + thread = std::thread(&OfdmDecoder::workerthread, this); +} + +OfdmDecoder::~OfdmDecoder() +{ + running = false; + pending_symbols_cv.notify_all(); + if (thread.joinable()) { + thread.join(); + } +} + +void OfdmDecoder::reset() +{ + running = false; + pending_symbols_cv.notify_all(); + if (thread.joinable()) { + thread.join(); + } + + thread = std::thread(&OfdmDecoder::workerthread, this); +} + +/** + * The code in the thread executes a simple loop, + * waiting for the next symbols and executing the interpretation + * operation for that symbols. + */ +void OfdmDecoder::workerthread() +{ + int currentSym = 0; + + running = true; + + while (running) { + std::unique_lock lock(mutex); + pending_symbols_cv.wait_for(lock, std::chrono::milliseconds(100)); + + if (currentSym == 0) { + constellationPoints.clear(); + constellationPoints.reserve( + (params.L-1) * params.K / constellationDecimation); + } + + while (num_pending_symbols > 0 && running) { + + if (currentSym == 0) + processPRS(); + else + decodeDataSymbol(currentSym); + + currentSym = (currentSym + 1) % (params.L); + num_pending_symbols -= 1; + + if (currentSym == 0) { + radioInterface.onConstellationPoints( + std::move(constellationPoints)); + constellationPoints.clear(); + constellationPoints.reserve( + (params.L-1) * params.K / constellationDecimation); + } + } + } + + //std::clog << "OFDM-decoder:" << "closing down now" << std::endl; +} + +void OfdmDecoder::pushAllSymbols(std::vector >&& syms) +{ + std::unique_lock lock(mutex); + + pending_symbols = std::move(syms); + num_pending_symbols = pending_symbols.size(); + pending_symbols_cv.notify_one(); +} + +/** + * handle symbol 0 as collected from the buffer + */ +void OfdmDecoder::processPRS() +{ + PROFILE(ProcessPRS); + memcpy (fft_buffer, + pending_symbols[0].data(), + params.T_u * sizeof(DSPCOMPLEX)); + fft_handler.do_FFT (); + /** + * The SNR is determined by looking at a segment of bins + * within the signal region and bits outside. + * It is just an indication + */ + snr = 0.7 * snr + 0.3 * get_snr(fft_buffer, 1); + if (++snrCount > 10) { + radioInterface.onSNR(snr); + snrCount = 0; + } + /** + * we are now in the frequency domain, and we keep the carriers + * as coming from the FFT as phase reference. + */ + memcpy(phaseReference.data(), fft_buffer, params.T_u * sizeof (DSPCOMPLEX)); +} + +/** + * For the other symbols, the first step is to go from + * time to frequency domain, to get the carriers. + * + * \brief decodeDataSymbol + * do the transforms and hand over the result to the fichandler or mschandler + */ +void OfdmDecoder::decodeDataSymbol(int32_t sym_ix) +{ + PROFILE(ProcessSymbol); + memcpy (fft_buffer, + pending_symbols[sym_ix].data() + T_g, + params.T_u * sizeof (DSPCOMPLEX)); + //fftlabel: + /** + * first step: do the FFT + */ + fft_handler.do_FFT(); + + /** + * a little optimization: we do not interchange the + * positive/negative frequencies to their right positions. + * The de-interleaving understands this + */ + + PROFILE(Deinterleaver); + /** + * Note that from here on, we are only interested in the + * K useful carriers of the FFT output + */ + for (int16_t i = 0; i < params.K; i ++) { + int16_t index = interleaver.mapIn(i); + if (index < 0) + index += params.T_u; + /** + * decoding is computing the phase difference between + * carriers with the same index in subsequent symbols. + * The carrier of a symbols is the reference for the carrier + * on the same position in the next symbols + */ + const DSPCOMPLEX r1 = fft_buffer[index] * conj (phaseReference[index]); + phaseReference[index] = fft_buffer[index]; + const DSPFLOAT ab1 = 127.0f / l1_norm(r1); + /// split the real and the imaginary part and scale it + + ibits[i] = -real (r1) * ab1; + ibits[params.K + i] = -imag (r1) * ab1; + + if (i % constellationDecimation == 0) { + constellationPoints.push_back(r1); + } + } + + if (sym_ix < 4) { + PROFILE(FICHandler); + ficHandler.processFicBlock(ibits.data(), sym_ix); + } + else { + PROFILE(MSCHandler); + mscHandler.processMscBlock(ibits.data(), sym_ix); + } + PROFILE(SymbolProcessed); +} + +/** + * for the snr we have a full T_u wide vector, with in the middle + * K carriers. + * Just get the strength from the selected carriers compared + * to the strength of the carriers outside that region + * method: 0 Jans method. This method are originally developed by Jan and is not working if neighbor channels are used because it uses occupied bins for the noise calculation + * 1 New method. This method is working also if neighbor channels are used + */ +int16_t OfdmDecoder::get_snr(DSPCOMPLEX *v, uint8_t method) +{ + int16_t i; + DSPFLOAT noise = 0; + DSPFLOAT signal = 0; + const auto T_u = params.T_u; + const auto K = params.K; + int16_t low = T_u / 2 - K / 2; + int16_t high = low + K; + + if(method) + { + for (i = 70; i < low - 20; i ++) // low - 90 samples + noise += abs (v[(T_u / 2 + i) % T_u]); + + for (i = high + 20; i < high + 120; i ++) // 100 samples + noise += abs (v[(T_u / 2 + i) % T_u]); + + noise /= (low - 90 + 100); + for (i = T_u / 2 - K / 4; i < T_u / 2 + K / 4; i ++) + signal += abs (v[(T_u / 2 + i) % T_u]); + + const auto dB_signal_new = get_db_over_256(signal / (K / 2)); + const auto dB_noise_new = get_db_over_256(noise); + const auto snr_new = dB_signal_new - dB_noise_new; + return snr_new; + } + else + { + noise = 0; + signal = 0; + for (i = 10; i < low - 20; i ++) + noise += abs (v[(T_u / 2 + i) % T_u]); + + for (i = high + 20; i < T_u - 10; i ++) + noise += abs (v[(T_u / 2 + i) % T_u]); + + noise /= (low - 30 + T_u - high - 30); + for (i = T_u / 2 - K / 4; i < T_u / 2 + K / 4; i ++) + signal += abs (v[(T_u / 2 + i) % T_u]); + + const auto dB_signal_old = get_db_over_256(signal / (K / 2)); + const auto dB_noise_old = get_db_over_256(noise); + const auto snr_old = dB_signal_old - dB_noise_old; + return snr_old; + } +} diff --git a/src/dabdsp/ofdm-decoder.h b/src/dabdsp/ofdm-decoder.h index 6bc722e..ed6f023 100644 --- a/src/dabdsp/ofdm-decoder.h +++ b/src/dabdsp/ofdm-decoder.h @@ -1,93 +1,93 @@ -/* - * Copyright (C) 2018 - * Matthias P. Braendli (matthias.braendli@mpb.li) - * - * Copyright (C) 2013 - * Jan van Katwijk (J.vanKatwijk@gmail.com) - * Lazy Chair Programming - * - * This file is part of the SDR-J (JSDR). - * SDR-J is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * SDR-J 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with SDR-J; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ -#ifndef __OFDM_DECODER -#define __OFDM_DECODER - -#include -#include -#include -#include -#include -#include -#include -#include "fft.h" -#include "dab-constants.h" -#include "freq-interleaver.h" -#include "radio-controller.h" -#include "fic-handler.h" -#include "msc-handler.h" - -class OfdmDecoder -{ - public: - OfdmDecoder( - const DABParams& p, - RadioControllerInterface& mr, - FicHandler& ficHandler, - MscHandler& mscHandler); - ~OfdmDecoder(); - void pushAllSymbols(std::vector >&& sym); - void reset(); - private: - int16_t get_snr(DSPCOMPLEX *, uint8_t method); - - const DABParams& params; - RadioControllerInterface& radioInterface; - FicHandler& ficHandler; - MscHandler& mscHandler; - std::atomic running = ATOMIC_VAR_INIT(false); - - std::condition_variable pending_symbols_cv; - std::mutex mutex; - int num_pending_symbols = 0; - std::vector > pending_symbols; - - std::thread thread; - void workerthread(void); - void processPRS(); - void decodeDataSymbol(int32_t n); - - int32_t T_g; - std::vector phaseReference; - fft::Forward fft_handler; - DSPCOMPLEX *fft_buffer; - FrequencyInterleaver interleaver; - - std::vector ibits; - int16_t snrCount = 0; - float snr = 0; - - //const double mer_alpha = 1e-7; - std::atomic mer = ATOMIC_VAR_INIT(0.0); - - public: - // Plotting all points is too costly, we decimate the number of points. - // The decimation factor should divide K for all transmission modes. - static const size_t constellationDecimation = 96; - private: - std::vector constellationPoints; -}; - -#endif - +/* + * Copyright (C) 2018 + * Matthias P. Braendli (matthias.braendli@mpb.li) + * + * Copyright (C) 2013 + * Jan van Katwijk (J.vanKatwijk@gmail.com) + * Lazy Chair Programming + * + * This file is part of the SDR-J (JSDR). + * SDR-J is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * SDR-J 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with SDR-J; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __OFDM_DECODER +#define __OFDM_DECODER + +#include +#include +#include +#include +#include +#include +#include +#include "fft.h" +#include "dab-constants.h" +#include "freq-interleaver.h" +#include "radio-controller.h" +#include "fic-handler.h" +#include "msc-handler.h" + +class OfdmDecoder +{ + public: + OfdmDecoder( + const DABParams& p, + RadioControllerInterface& mr, + FicHandler& ficHandler, + MscHandler& mscHandler); + ~OfdmDecoder(); + void pushAllSymbols(std::vector >&& sym); + void reset(); + private: + int16_t get_snr(DSPCOMPLEX *, uint8_t method); + + const DABParams& params; + RadioControllerInterface& radioInterface; + FicHandler& ficHandler; + MscHandler& mscHandler; + std::atomic running = ATOMIC_VAR_INIT(false); + + std::condition_variable pending_symbols_cv; + std::mutex mutex; + int num_pending_symbols = 0; + std::vector > pending_symbols; + + std::thread thread; + void workerthread(void); + void processPRS(); + void decodeDataSymbol(int32_t n); + + int32_t T_g; + std::vector phaseReference; + fft::Forward fft_handler; + DSPCOMPLEX *fft_buffer; + FrequencyInterleaver interleaver; + + std::vector ibits; + int16_t snrCount = 0; + float snr = 0; + + //const double mer_alpha = 1e-7; + std::atomic mer = ATOMIC_VAR_INIT(0.0); + + public: + // Plotting all points is too costly, we decimate the number of points. + // The decimation factor should divide K for all transmission modes. + static const size_t constellationDecimation = 96; + private: + std::vector constellationPoints; +}; + +#endif + diff --git a/src/dabdsp/ofdm-processor.cpp b/src/dabdsp/ofdm-processor.cpp index 4e68413..a3391c2 100644 --- a/src/dabdsp/ofdm-processor.cpp +++ b/src/dabdsp/ofdm-processor.cpp @@ -1,651 +1,651 @@ -/* - * Copyright (C) 2018 - * Matthias P. Braendli (matthias.braendli@mpb.li) - * - * Copyright (C) 2017 - * Albrecht Lohofener (albrechtloh@gmx.de) - * - * This file is based on SDR-J - * Copyright (C) 2010, 2011, 2012, 2013 - * Jan van Katwijk (J.vanKatwijk@gmail.com) - * - * This file is part of the welle.io. - * Many of the ideas as implemented in welle.io are derived from - * other work, made available through the GNU general Public License. - * All copyrights of the original authors are recognized. - * - * welle.io is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * welle.io 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with welle.io; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - -#include -#include "ofdm-processor.h" -#include "profiling.h" -#include -#include - -#ifdef _WINDOWS -#define _USE_MATH_DEFINES -#include -#endif - -// -#define SEARCH_RANGE (2 * 36) -#define CORRELATION_LENGTH 24 - -/** - * \brief OFDMProcessor - * The OFDMProcessor class is the driver of the processing - * of the samplestream. - * It takes as parameter (a.o) the handler for the - * input device as well as the interpreters for - * FIC symbols and for MSC symbols. - * Local is a class ofdmDecoder that will - as the name suggests - - * map samples to bits and that will pass on the bits - * to the interpreters for FIC and MSC - */ - - -OFDMProcessor::OFDMProcessor( - InputInterface& inputInterface, - const DABParams& params, - RadioControllerInterface& ri, - MscHandler& msc, - FicHandler& fic, - RadioReceiverOptions rro) : - receiver_options(rro), - radioInterface(ri), - input(inputInterface), - params(params), - ficHandler(fic), - tiiDecoder(params, ri), - T_null(params.T_null), - T_u(params.T_u), - T_s(params.T_s), - T_F(params.T_F), - oscillatorTable(INPUT_RATE), - phaseRef(params, rro.fftPlacementMethod), - ofdmDecoder(params, ri, fic, msc), - fft_handler(params.T_u), - fft_buffer(fft_handler.getVector()) -{ - /** - * the class phaseReference will take a number of samples - * and indicate - using some threshold - whether there is - * a strong correlation or not. - * It is used to decide on the first non-null sample - * of the frame. - * The size of the symbols handed over for inspection - * is T_u - */ - /** - * the ofdmDecoder takes time domain samples, will do an FFT, - * map the result on (soft) bits and hand over control for handling - * the decoded symbols - */ - - for (int i = 0; i < INPUT_RATE; i ++) - oscillatorTable[i] = DSPCOMPLEX(cos(2.0 * M_PI * i / INPUT_RATE), - sin(2.0 * M_PI * i / INPUT_RATE)); - - // and for the correlation - refArg.resize(CORRELATION_LENGTH); - for (int i = 0; i < CORRELATION_LENGTH; i ++) { - refArg[i] = arg(phaseRef[(T_u + i) % T_u] * - conj(phaseRef[(T_u + i + 1) % T_u])); - } - - correlationVector.resize(SEARCH_RANGE + CORRELATION_LENGTH); -} - -OFDMProcessor::~OFDMProcessor() -{ - running = false; - - if (threadHandle.joinable()) { - threadHandle.join(); - } -} - -void OFDMProcessor::restart() -{ - //std::clog << "OFDM-processor:restart" << std::endl; - - running = false; - if (threadHandle.joinable()) { - threadHandle.join(); - } - - coarseCorrector = 0; - fineCorrector = 0; - syncBufferIndex = 0; - sLevel = 0; - localPhase = 0; - input.restart(); - running = true; - threadHandle = std::thread(&OFDMProcessor::run, this); -} - -class InputFailure { }; -class NotRunningAnymore { }; - -/** - * \brief getSample - * Profiling shows that getting a sample, together - * with the frequency shift, is a real performance killer. - * we therefore distinguish between getting a single sample - * and getting a vector full of samples - */ - -DSPCOMPLEX OFDMProcessor::getSample(int32_t phase) -{ - DSPCOMPLEX temp; - if (!running) - throw NotRunningAnymore(); - /// bufferContent is an indicator for the value of ...->Samples () - if (bufferContent == 0) { - bufferContent = input.getSamplesToRead (); - while ((bufferContent == 0) && running) { - if (not input.is_ok()) { - throw InputFailure(); - } - std::this_thread::sleep_for(std::chrono::microseconds(10)); - bufferContent = input.getSamplesToRead (); - } - } - - if (!running) - throw NotRunningAnymore(); - // - // so here, bufferContent > 0 - input.getSamples (&temp, 1); - bufferContent --; - - // - // OK, we have a sample!! - // first: adjust frequency. We need Hz accuracy - localPhase -= phase; - localPhase = (localPhase + INPUT_RATE) % INPUT_RATE; - temp *= oscillatorTable[localPhase]; - sLevel = 0.00001 * l1_norm(temp) + (1 - 0.00001) * sLevel; -#define N 5 - sampleCnt ++; - if (++ sampleCnt > INPUT_RATE / N) { - radioInterface.onFrequencyCorrectorChange( - fineCorrector, coarseCorrector); - sampleCnt = 0; - } - return temp; -} - -void OFDMProcessor::getSamples(DSPCOMPLEX *v, int16_t n, int32_t phase) -{ - int32_t i; - - if (!running) - throw NotRunningAnymore(); - if (n > bufferContent) { - bufferContent = input.getSamplesToRead (); - while ((bufferContent < n) && running) { - if (not input.is_ok()) { - throw InputFailure(); - } - std::this_thread::sleep_for(std::chrono::microseconds(10)); - bufferContent = input.getSamplesToRead(); - } - } - if (!running) - throw NotRunningAnymore(); - // - // so here, bufferContent >= n - n = input.getSamples (v, n); - bufferContent -= n; - - // OK, we have samples!! - // first: adjust frequency. We need Hz accuracy - for (i = 0; i < n; i ++) { - localPhase -= phase; - localPhase = (localPhase + INPUT_RATE) % INPUT_RATE; - v[i] *= oscillatorTable[localPhase]; - sLevel = 0.00001 * l1_norm(v[i]) + (1 - 0.00001) * sLevel; - } - - sampleCnt += n; - if (sampleCnt > INPUT_RATE / N) { - radioInterface.onFrequencyCorrectorChange( - fineCorrector, coarseCorrector); - sampleCnt = 0; - } -} - - -/*** - * \brief run - * The main thread, reading samples, - * time synchronization and frequency synchronization - * Identifying symbols in the DAB frame - * and sending them to the ofdmDecoder who will transfer the results - * Finally, estimating the small frequency error - */ -void OFDMProcessor::run() -{ - int32_t startIndex; - int32_t i; - int32_t counter; - float currentStrength; - constexpr int32_t syncBufferSize = 32768; - constexpr int32_t syncBufferMask = syncBufferSize - 1; - float envBuffer[syncBufferSize]; - - std::vector ofdmBuffer(params.L * params.T_s); - std::vector > allSymbols; - - try { - - //Initing: - /// first, we need samples to get a reasonable sLevel - sLevel = 0; - for (i = 0; i < T_F / 2; i ++) { - l1_norm(getSample (0)); - } -notSynced: - PROFILE(NotSynced); - if (scanMode && ++attempts > 5) { - radioInterface.onSignalPresence(false); - scanMode = false; - attempts = 0; - } - syncBufferIndex = 0; - currentStrength = 0; - - // read in T_s samples for a next attempt; - syncBufferIndex = 0; - currentStrength = 0; - for (i = 0; i < 50; i ++) { - DSPCOMPLEX sample = getSample (0); - envBuffer [syncBufferIndex] = l1_norm(sample); - currentStrength += envBuffer [syncBufferIndex]; - syncBufferIndex ++; - } - /** - * We now have initial values for currentStrength (i.e. the sum - * over the last 50 samples) and sLevel, the long term average. - */ - //SyncOnNull: - /** - * here we start looking for the null level, i.e. a dip - */ - counter = 0; - radioInterface.onSyncChange(false); - while (currentStrength / 50 > 0.50 * sLevel) { - DSPCOMPLEX sample = - getSample (coarseCorrector + fineCorrector); - envBuffer [syncBufferIndex] = l1_norm(sample); - // update the levels - currentStrength += envBuffer [syncBufferIndex] - - envBuffer [(syncBufferIndex - 50) & syncBufferMask]; - syncBufferIndex = (syncBufferIndex + 1) & syncBufferMask; - counter ++; - if (counter > T_F) { // hopeless - // fprintf (stderr, "%f %f\n", currentStrength / 50, sLevel); - goto notSynced; - } - } - /** - * It seemed we found a dip that started app 65/100 * 50 samples earlier. - * We now start looking for the end of the null period. - */ - counter = 0; - //SyncOnEndNull: - PROFILE(SyncOnEndNull); - while (currentStrength / 50 < 0.75 * sLevel) { - DSPCOMPLEX sample = getSample (coarseCorrector + fineCorrector); - envBuffer [syncBufferIndex] = l1_norm(sample); - // update the levels - currentStrength += envBuffer [syncBufferIndex] - - envBuffer [(syncBufferIndex - 50) & syncBufferMask]; - syncBufferIndex = (syncBufferIndex + 1) & syncBufferMask; - counter ++; - // - if (counter > T_null + 50) { // hopeless - //std::clog << "ofdm-processor: " << "SyncOnEndNull failed" << std::endl; - goto notSynced; - } - } - /** - * The end of the null period is identified, probably about 40 - * samples earlier. - */ -SyncOnPhase: - PROFILE(SyncOnPhase); - /** - * We now have to find the exact first sample of the non-null period. - * We use a correlation that will find the first sample after the - * cyclic prefix. - * When in "sync", i.e. pretty sure that we know were we are, - * we skip the "dip" identification and come here right away. - * - * now read in Tu samples. The precise number is not really important - * as long as we can be sure that the first sample to be identified - * is part of the samples read. - */ - getSamples(ofdmBuffer.data(), T_u, coarseCorrector + fineCorrector); - // - /// and then, call upon the phase synchronizer to verify/compute - /// the real "first" sample - startIndex = phaseRef.findIndex(ofdmBuffer.data(), - impulseResponseBuffer); - PROFILE(FindIndex); - radioInterface.onNewImpulseResponse(std::move(impulseResponseBuffer)); - impulseResponseBuffer.clear(); - - if (startIndex < 0) { // no sync, try again - //std::clog << "ofdm-processor: " << "SyncOnPhase failed" << std::endl; - goto notSynced; - } - if (scanMode) { - radioInterface.onSignalPresence(true); - scanMode = false; - attempts = 0; - } - - /** - * Once here, we are synchronized, we need to copy the data we - * used for synchronization for the PRS */ - memmove(ofdmBuffer.data(), &ofdmBuffer[startIndex], - (params.T_u - startIndex) * sizeof (DSPCOMPLEX)); - ofdmBufferIndex = params.T_u - startIndex; - - //Symbol 0: Phase reference symbol symbol - /** - * Symbol 0 is special in that it is used for fine time synchronization - * and its content is used as a reference for decoding the - * first data symbol. - * We read the missing samples in the ofdm buffer - */ - radioInterface.onSyncChange(true); - getSamples(&ofdmBuffer[ofdmBufferIndex], - T_u - ofdmBufferIndex, - coarseCorrector + fineCorrector); - - RadioReceiverOptions rro; - { - std::lock_guard lock(receiver_options_mutex); - rro = receiver_options; - } - - std::vector prs; - if (rro.decodeTII) { - prs.resize(T_u); - std::copy(ofdmBuffer.begin(), ofdmBuffer.begin() + T_u, prs.begin()); - } - - // Here we look only at the PRS when we need a coarse - // frequency synchronization. - // The width is limited to 2 * 35 kHz (i.e. positive and negative) - // - // We inhibit touching the coarse corrector if more than 50% of - // FICs had correct CRC, because enabling the coarse corrector during a short - // reception glitch might provoke a long delay until it resyncs properly. - // As long as some FICs have correct CRC, we assume the coarse corrector cannot - // be off. - if (!rro.disableCoarseCorrector and ficHandler.getFicDecodeRatioPercent() < 50) { - if (!coarseSyncCounter) { - //std::clog << "ofdm-processor: " << "Lost coarse sync (coarseCorrector: " << lastValidCoarseCorrector << "; fineCorrector: " << lastValidFineCorrector << ")" << std::endl; - } - - coarseSyncCounter++; - int correction = processPRS(ofdmBuffer.data(), rro.freqsyncMethod); - if (correction != 100) { - coarseCorrector += correction * params.carrierDiff; - if (abs (coarseCorrector) > (35 * 1000 * 1000)) // MB: Was KHz(35) - coarseCorrector = 0; - } - } - else { - if (coarseSyncCounter) { - //std::clog << "ofdm-processor: " << "Found sync (coarseCorrector: " << lastValidCoarseCorrector << "; fineCorrector: " << lastValidFineCorrector << " after " << coarseSyncCounter << " frames)" << std::endl; - } - coarseSyncCounter = 0; - - lastValidFineCorrector = fineCorrector; - lastValidCoarseCorrector = coarseCorrector; - } - - allSymbols.resize(params.L); - allSymbols[0] = move(ofdmBuffer); - ofdmBuffer.resize(params.L * params.T_s); - - /** - * after symbol 0, we will just read in the other (params.L - 1) symbols - */ - //Data_symbols: - PROFILE(DataSymbols); - /** - * The first ones are the FIC symbols, followed by all MSC - * symbols. We immediately start with building up an average of the - * phase difference between the samples in the cyclic prefix and the - * corresponding samples in the datapart. - */ - DSPCOMPLEX FreqCorr = DSPCOMPLEX(0, 0); - for (int sym = 1; sym < params.L; sym ++) { - auto& buf = allSymbols[sym]; - buf.resize(T_s); - getSamples(buf.data(), T_s, coarseCorrector + fineCorrector); - for (int i = T_u; i < T_s; i ++) - FreqCorr += buf[i] * conj(buf[i - T_u]); - } - - PROFILE(PushAllSymbols); - ofdmDecoder.pushAllSymbols(move(allSymbols)); - - //NewOffset: - /// we integrate the newly found frequency error with the - /// existing frequency error. - fineCorrector += 0.1 * arg(FreqCorr) / M_PI * - (params.carrierDiff / 2); - // - /** - * OK, here we are at the end of the frame - * Assume everything went well and skip T_null samples - */ - syncBufferIndex = 0; - currentStrength = 0; - - PROFILE(DecodeTII); - // The NULL is interesting to save because it carries the TII. - std::vector nullSymbol(T_null); - getSamples(nullSymbol.data(), T_null, coarseCorrector + fineCorrector); - if (rro.decodeTII) { - tiiDecoder.pushSymbols(nullSymbol, prs); - } - - PROFILE(OnNewNull); - radioInterface.onNewNullSymbol(std::move(nullSymbol)); - - /** - * The first sample to be found for the next frame should be T_g - * samples ahead - * Here we just check the fineCorrector - */ - counter = 0; - - if (fineCorrector > params.carrierDiff / 2) { - coarseCorrector += params.carrierDiff; - fineCorrector -= params.carrierDiff; - } - else - if (fineCorrector < -params.carrierDiff / 2) { - coarseCorrector -= params.carrierDiff; - fineCorrector += params.carrierDiff; - } - //ReadyForNewFrame: - /// and off we go, up to the next frame - PROFILE_FRAME_DECODED(); - goto SyncOnPhase; - } - catch (const NotRunningAnymore&) { - //std::clog << "OFDM-processor: closing down" << std::endl; - } - catch (const InputFailure&) { - //std::clog << "OFDM-processor: input not ok, closing down" << std::endl; - running = false; //Needed before onInputFailure, because subsequent calls will call OFDMProcessor::stop() - radioInterface.onInputFailure(); - } - running = false; -} - -void OFDMProcessor::stop() -{ - if (running) { - running = false; - if (threadHandle.joinable()) { - threadHandle.join(); - } - } -} - -void OFDMProcessor::resetCoarseCorrector() -{ - coarseCorrector = 0; -} - -void OFDMProcessor::setReceiverOptions(const RadioReceiverOptions rro) -{ - std::unique_lock lock(receiver_options_mutex); - bool need_reset = (receiver_options.disableCoarseCorrector != rro.disableCoarseCorrector); - receiver_options = rro; - phaseRef.selectFFTWindowPlacement(rro.fftPlacementMethod); - lock.unlock(); - - if (need_reset) { - restart(); - } -} - -void OFDMProcessor::set_scanMode(bool b) -{ - scanMode = b; -} - -#define RANGE 36 -int16_t OFDMProcessor::processPRS(DSPCOMPLEX *v, const FreqsyncMethod& freqsyncMethod) -{ - int16_t i, j, index = 100; - - memcpy(fft_buffer, v, T_u * sizeof(DSPCOMPLEX)); - fft_handler.do_FFT(); - - switch (freqsyncMethod) { - case FreqsyncMethod::GetMiddle: - return getMiddle(fft_buffer); - case FreqsyncMethod::CorrelatePRS: - { - // The "best" approach for computing the coarse frequency - // offset is to look at the spectrum of symbol 0 and relate that - // with the spectrum as it should be, i.e. the refTable - // However, since there might be - // a pretty large phase offset between the incoming data and - // the reference table data, we correlate the - // phase differences between the subsequent carriers rather - // than the values in the segments themselves. - // It seems to work pretty well - // - // The phase differences are computed once - for (i = 0; i < SEARCH_RANGE + CORRELATION_LENGTH; i ++) { - int16_t baseIndex = T_u - SEARCH_RANGE / 2 + i; - correlationVector[i] = - arg(fft_buffer[baseIndex % T_u] * - conj(fft_buffer[(baseIndex + 1) % T_u])); - } - - float MMax = 0; - for (i = 0; i < SEARCH_RANGE; i ++) { - float sum = 0; - for (j = 0; j < CORRELATION_LENGTH; j ++) { - sum += abs(refArg [j] * correlationVector[i + j]); - if (sum > MMax) { - MMax = sum; - index = i; - } - } - } - - // Now map the index back to the right carrier - return T_u - SEARCH_RANGE / 2 + index - T_u; - } - case FreqsyncMethod::PatternOfZeros: - { - // An alternative way is to look at a special pattern consisting - // of zeros in the row of args between successive carriers. - float Mmin = 1000; - for (i = T_u - SEARCH_RANGE / 2; i < T_u + SEARCH_RANGE / 2; i ++) { - float a1 = abs (abs (arg (fft_buffer [(i + 1) % T_u] * - conj (fft_buffer [(i + 2) % T_u])) / M_PI) - 1); - float a2 = abs (abs (arg (fft_buffer [(i + 2) % T_u] * - conj (fft_buffer [(i + 3) % T_u])) / M_PI) - 1); - float a3 = abs (arg (fft_buffer [(i + 3) % T_u] * - conj (fft_buffer [(i + 4) % T_u]))); - float a4 = abs (arg (fft_buffer [(i + 4) % T_u] * - conj (fft_buffer [(i + 5) % T_u]))); - float a5 = abs (arg (fft_buffer [(i + 5) % T_u] * - conj (fft_buffer [(i + 6) % T_u]))); - float b1 = abs (abs (arg (fft_buffer [(i + 16 + 1) % T_u] * - conj (fft_buffer [(i + 16 + 3) % T_u])) / M_PI) - 1); - float b2 = abs (arg (fft_buffer [(i + 16 + 3) % T_u] * - conj (fft_buffer [(i + 16 + 4) % T_u]))); - float b3 = abs (arg (fft_buffer [(i + 16 + 4) % T_u] * - conj (fft_buffer [(i + 16 + 5) % T_u]))); - float b4 = abs (arg (fft_buffer [(i + 16 + 5) % T_u] * - conj (fft_buffer [(i + 16 + 6) % T_u]))); - float sum = a1 + a2 + a3 + a4 + a5 + b1 + b2 + b3 + b4; - if (sum < Mmin) { - Mmin = sum; - index = i; - } - } - return index - T_u; - } - } - throw std::logic_error("Unimplemented freqsyncMethod"); -} - -int16_t OFDMProcessor::getMiddle (DSPCOMPLEX *v) -{ - int16_t i; - DSPFLOAT sum = 0; - int16_t maxIndex = 0; - DSPFLOAT oldMax = 0; - // - // basic sum over K carriers that are - most likely - - // in the range - // The range in which the carrier should be is - // T_u / 2 - K / 2 .. T_u / 2 + K / 2 - // We first determine an initial sum over params.K carriers - for (i = 40; i < params.K + 40; i ++) - sum += abs (v [(T_u / 2 + i) % T_u]); - // - // Now a moving sum, look for a maximum within a reasonable - // range (around (T_u - K) / 2, the start of the useful frequencies) - for (i = 40; i < T_u - (params.K - 40); i ++) { - sum -= abs (v [(T_u / 2 + i) % T_u]); - sum += abs (v [(T_u / 2 + i + params.K) % T_u]); - if (sum > oldMax) { - sum = oldMax; - maxIndex = i; - } - } - return maxIndex - (T_u - params.K) / 2; -} +/* + * Copyright (C) 2018 + * Matthias P. Braendli (matthias.braendli@mpb.li) + * + * Copyright (C) 2017 + * Albrecht Lohofener (albrechtloh@gmx.de) + * + * This file is based on SDR-J + * Copyright (C) 2010, 2011, 2012, 2013 + * Jan van Katwijk (J.vanKatwijk@gmail.com) + * + * This file is part of the welle.io. + * Many of the ideas as implemented in welle.io are derived from + * other work, made available through the GNU general Public License. + * All copyrights of the original authors are recognized. + * + * welle.io is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * welle.io 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with welle.io; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include "ofdm-processor.h" +#include "profiling.h" +#include +#include + +#ifdef _WINDOWS +#define _USE_MATH_DEFINES +#include +#endif + +// +#define SEARCH_RANGE (2 * 36) +#define CORRELATION_LENGTH 24 + +/** + * \brief OFDMProcessor + * The OFDMProcessor class is the driver of the processing + * of the samplestream. + * It takes as parameter (a.o) the handler for the + * input device as well as the interpreters for + * FIC symbols and for MSC symbols. + * Local is a class ofdmDecoder that will - as the name suggests - + * map samples to bits and that will pass on the bits + * to the interpreters for FIC and MSC + */ + + +OFDMProcessor::OFDMProcessor( + InputInterface& inputInterface, + const DABParams& params, + RadioControllerInterface& ri, + MscHandler& msc, + FicHandler& fic, + RadioReceiverOptions rro) : + receiver_options(rro), + radioInterface(ri), + input(inputInterface), + params(params), + ficHandler(fic), + tiiDecoder(params, ri), + T_null(params.T_null), + T_u(params.T_u), + T_s(params.T_s), + T_F(params.T_F), + oscillatorTable(INPUT_RATE), + phaseRef(params, rro.fftPlacementMethod), + ofdmDecoder(params, ri, fic, msc), + fft_handler(params.T_u), + fft_buffer(fft_handler.getVector()) +{ + /** + * the class phaseReference will take a number of samples + * and indicate - using some threshold - whether there is + * a strong correlation or not. + * It is used to decide on the first non-null sample + * of the frame. + * The size of the symbols handed over for inspection + * is T_u + */ + /** + * the ofdmDecoder takes time domain samples, will do an FFT, + * map the result on (soft) bits and hand over control for handling + * the decoded symbols + */ + + for (int i = 0; i < INPUT_RATE; i ++) + oscillatorTable[i] = DSPCOMPLEX(cos(2.0 * M_PI * i / INPUT_RATE), + sin(2.0 * M_PI * i / INPUT_RATE)); + + // and for the correlation + refArg.resize(CORRELATION_LENGTH); + for (int i = 0; i < CORRELATION_LENGTH; i ++) { + refArg[i] = arg(phaseRef[(T_u + i) % T_u] * + conj(phaseRef[(T_u + i + 1) % T_u])); + } + + correlationVector.resize(SEARCH_RANGE + CORRELATION_LENGTH); +} + +OFDMProcessor::~OFDMProcessor() +{ + running = false; + + if (threadHandle.joinable()) { + threadHandle.join(); + } +} + +void OFDMProcessor::restart() +{ + //std::clog << "OFDM-processor:restart" << std::endl; + + running = false; + if (threadHandle.joinable()) { + threadHandle.join(); + } + + coarseCorrector = 0; + fineCorrector = 0; + syncBufferIndex = 0; + sLevel = 0; + localPhase = 0; + input.restart(); + running = true; + threadHandle = std::thread(&OFDMProcessor::run, this); +} + +class InputFailure { }; +class NotRunningAnymore { }; + +/** + * \brief getSample + * Profiling shows that getting a sample, together + * with the frequency shift, is a real performance killer. + * we therefore distinguish between getting a single sample + * and getting a vector full of samples + */ + +DSPCOMPLEX OFDMProcessor::getSample(int32_t phase) +{ + DSPCOMPLEX temp; + if (!running) + throw NotRunningAnymore(); + /// bufferContent is an indicator for the value of ...->Samples () + if (bufferContent == 0) { + bufferContent = input.getSamplesToRead (); + while ((bufferContent == 0) && running) { + if (not input.is_ok()) { + throw InputFailure(); + } + std::this_thread::sleep_for(std::chrono::microseconds(10)); + bufferContent = input.getSamplesToRead (); + } + } + + if (!running) + throw NotRunningAnymore(); + // + // so here, bufferContent > 0 + input.getSamples (&temp, 1); + bufferContent --; + + // + // OK, we have a sample!! + // first: adjust frequency. We need Hz accuracy + localPhase -= phase; + localPhase = (localPhase + INPUT_RATE) % INPUT_RATE; + temp *= oscillatorTable[localPhase]; + sLevel = 0.00001 * l1_norm(temp) + (1 - 0.00001) * sLevel; +#define N 5 + sampleCnt ++; + if (++ sampleCnt > INPUT_RATE / N) { + radioInterface.onFrequencyCorrectorChange( + fineCorrector, coarseCorrector); + sampleCnt = 0; + } + return temp; +} + +void OFDMProcessor::getSamples(DSPCOMPLEX *v, int16_t n, int32_t phase) +{ + int32_t i; + + if (!running) + throw NotRunningAnymore(); + if (n > bufferContent) { + bufferContent = input.getSamplesToRead (); + while ((bufferContent < n) && running) { + if (not input.is_ok()) { + throw InputFailure(); + } + std::this_thread::sleep_for(std::chrono::microseconds(10)); + bufferContent = input.getSamplesToRead(); + } + } + if (!running) + throw NotRunningAnymore(); + // + // so here, bufferContent >= n + n = input.getSamples (v, n); + bufferContent -= n; + + // OK, we have samples!! + // first: adjust frequency. We need Hz accuracy + for (i = 0; i < n; i ++) { + localPhase -= phase; + localPhase = (localPhase + INPUT_RATE) % INPUT_RATE; + v[i] *= oscillatorTable[localPhase]; + sLevel = 0.00001 * l1_norm(v[i]) + (1 - 0.00001) * sLevel; + } + + sampleCnt += n; + if (sampleCnt > INPUT_RATE / N) { + radioInterface.onFrequencyCorrectorChange( + fineCorrector, coarseCorrector); + sampleCnt = 0; + } +} + + +/*** + * \brief run + * The main thread, reading samples, + * time synchronization and frequency synchronization + * Identifying symbols in the DAB frame + * and sending them to the ofdmDecoder who will transfer the results + * Finally, estimating the small frequency error + */ +void OFDMProcessor::run() +{ + int32_t startIndex; + int32_t i; + int32_t counter; + float currentStrength; + constexpr int32_t syncBufferSize = 32768; + constexpr int32_t syncBufferMask = syncBufferSize - 1; + float envBuffer[syncBufferSize]; + + std::vector ofdmBuffer(params.L * params.T_s); + std::vector > allSymbols; + + try { + + //Initing: + /// first, we need samples to get a reasonable sLevel + sLevel = 0; + for (i = 0; i < T_F / 2; i ++) { + l1_norm(getSample (0)); + } +notSynced: + PROFILE(NotSynced); + if (scanMode && ++attempts > 5) { + radioInterface.onSignalPresence(false); + scanMode = false; + attempts = 0; + } + syncBufferIndex = 0; + currentStrength = 0; + + // read in T_s samples for a next attempt; + syncBufferIndex = 0; + currentStrength = 0; + for (i = 0; i < 50; i ++) { + DSPCOMPLEX sample = getSample (0); + envBuffer [syncBufferIndex] = l1_norm(sample); + currentStrength += envBuffer [syncBufferIndex]; + syncBufferIndex ++; + } + /** + * We now have initial values for currentStrength (i.e. the sum + * over the last 50 samples) and sLevel, the long term average. + */ + //SyncOnNull: + /** + * here we start looking for the null level, i.e. a dip + */ + counter = 0; + radioInterface.onSyncChange(false); + while (currentStrength / 50 > 0.50 * sLevel) { + DSPCOMPLEX sample = + getSample (coarseCorrector + fineCorrector); + envBuffer [syncBufferIndex] = l1_norm(sample); + // update the levels + currentStrength += envBuffer [syncBufferIndex] - + envBuffer [(syncBufferIndex - 50) & syncBufferMask]; + syncBufferIndex = (syncBufferIndex + 1) & syncBufferMask; + counter ++; + if (counter > T_F) { // hopeless + // fprintf (stderr, "%f %f\n", currentStrength / 50, sLevel); + goto notSynced; + } + } + /** + * It seemed we found a dip that started app 65/100 * 50 samples earlier. + * We now start looking for the end of the null period. + */ + counter = 0; + //SyncOnEndNull: + PROFILE(SyncOnEndNull); + while (currentStrength / 50 < 0.75 * sLevel) { + DSPCOMPLEX sample = getSample (coarseCorrector + fineCorrector); + envBuffer [syncBufferIndex] = l1_norm(sample); + // update the levels + currentStrength += envBuffer [syncBufferIndex] - + envBuffer [(syncBufferIndex - 50) & syncBufferMask]; + syncBufferIndex = (syncBufferIndex + 1) & syncBufferMask; + counter ++; + // + if (counter > T_null + 50) { // hopeless + //std::clog << "ofdm-processor: " << "SyncOnEndNull failed" << std::endl; + goto notSynced; + } + } + /** + * The end of the null period is identified, probably about 40 + * samples earlier. + */ +SyncOnPhase: + PROFILE(SyncOnPhase); + /** + * We now have to find the exact first sample of the non-null period. + * We use a correlation that will find the first sample after the + * cyclic prefix. + * When in "sync", i.e. pretty sure that we know were we are, + * we skip the "dip" identification and come here right away. + * + * now read in Tu samples. The precise number is not really important + * as long as we can be sure that the first sample to be identified + * is part of the samples read. + */ + getSamples(ofdmBuffer.data(), T_u, coarseCorrector + fineCorrector); + // + /// and then, call upon the phase synchronizer to verify/compute + /// the real "first" sample + startIndex = phaseRef.findIndex(ofdmBuffer.data(), + impulseResponseBuffer); + PROFILE(FindIndex); + radioInterface.onNewImpulseResponse(std::move(impulseResponseBuffer)); + impulseResponseBuffer.clear(); + + if (startIndex < 0) { // no sync, try again + //std::clog << "ofdm-processor: " << "SyncOnPhase failed" << std::endl; + goto notSynced; + } + if (scanMode) { + radioInterface.onSignalPresence(true); + scanMode = false; + attempts = 0; + } + + /** + * Once here, we are synchronized, we need to copy the data we + * used for synchronization for the PRS */ + memmove(ofdmBuffer.data(), &ofdmBuffer[startIndex], + (params.T_u - startIndex) * sizeof (DSPCOMPLEX)); + ofdmBufferIndex = params.T_u - startIndex; + + //Symbol 0: Phase reference symbol symbol + /** + * Symbol 0 is special in that it is used for fine time synchronization + * and its content is used as a reference for decoding the + * first data symbol. + * We read the missing samples in the ofdm buffer + */ + radioInterface.onSyncChange(true); + getSamples(&ofdmBuffer[ofdmBufferIndex], + T_u - ofdmBufferIndex, + coarseCorrector + fineCorrector); + + RadioReceiverOptions rro; + { + std::lock_guard lock(receiver_options_mutex); + rro = receiver_options; + } + + std::vector prs; + if (rro.decodeTII) { + prs.resize(T_u); + std::copy(ofdmBuffer.begin(), ofdmBuffer.begin() + T_u, prs.begin()); + } + + // Here we look only at the PRS when we need a coarse + // frequency synchronization. + // The width is limited to 2 * 35 kHz (i.e. positive and negative) + // + // We inhibit touching the coarse corrector if more than 50% of + // FICs had correct CRC, because enabling the coarse corrector during a short + // reception glitch might provoke a long delay until it resyncs properly. + // As long as some FICs have correct CRC, we assume the coarse corrector cannot + // be off. + if (!rro.disableCoarseCorrector and ficHandler.getFicDecodeRatioPercent() < 50) { + if (!coarseSyncCounter) { + //std::clog << "ofdm-processor: " << "Lost coarse sync (coarseCorrector: " << lastValidCoarseCorrector << "; fineCorrector: " << lastValidFineCorrector << ")" << std::endl; + } + + coarseSyncCounter++; + int correction = processPRS(ofdmBuffer.data(), rro.freqsyncMethod); + if (correction != 100) { + coarseCorrector += correction * params.carrierDiff; + if (abs (coarseCorrector) > (35 * 1000 * 1000)) // MB: Was KHz(35) + coarseCorrector = 0; + } + } + else { + if (coarseSyncCounter) { + //std::clog << "ofdm-processor: " << "Found sync (coarseCorrector: " << lastValidCoarseCorrector << "; fineCorrector: " << lastValidFineCorrector << " after " << coarseSyncCounter << " frames)" << std::endl; + } + coarseSyncCounter = 0; + + lastValidFineCorrector = fineCorrector; + lastValidCoarseCorrector = coarseCorrector; + } + + allSymbols.resize(params.L); + allSymbols[0] = move(ofdmBuffer); + ofdmBuffer.resize(params.L * params.T_s); + + /** + * after symbol 0, we will just read in the other (params.L - 1) symbols + */ + //Data_symbols: + PROFILE(DataSymbols); + /** + * The first ones are the FIC symbols, followed by all MSC + * symbols. We immediately start with building up an average of the + * phase difference between the samples in the cyclic prefix and the + * corresponding samples in the datapart. + */ + DSPCOMPLEX FreqCorr = DSPCOMPLEX(0, 0); + for (int sym = 1; sym < params.L; sym ++) { + auto& buf = allSymbols[sym]; + buf.resize(T_s); + getSamples(buf.data(), T_s, coarseCorrector + fineCorrector); + for (int i = T_u; i < T_s; i ++) + FreqCorr += buf[i] * conj(buf[i - T_u]); + } + + PROFILE(PushAllSymbols); + ofdmDecoder.pushAllSymbols(move(allSymbols)); + + //NewOffset: + /// we integrate the newly found frequency error with the + /// existing frequency error. + fineCorrector += 0.1 * arg(FreqCorr) / M_PI * + (params.carrierDiff / 2); + // + /** + * OK, here we are at the end of the frame + * Assume everything went well and skip T_null samples + */ + syncBufferIndex = 0; + currentStrength = 0; + + PROFILE(DecodeTII); + // The NULL is interesting to save because it carries the TII. + std::vector nullSymbol(T_null); + getSamples(nullSymbol.data(), T_null, coarseCorrector + fineCorrector); + if (rro.decodeTII) { + tiiDecoder.pushSymbols(nullSymbol, prs); + } + + PROFILE(OnNewNull); + radioInterface.onNewNullSymbol(std::move(nullSymbol)); + + /** + * The first sample to be found for the next frame should be T_g + * samples ahead + * Here we just check the fineCorrector + */ + counter = 0; + + if (fineCorrector > params.carrierDiff / 2) { + coarseCorrector += params.carrierDiff; + fineCorrector -= params.carrierDiff; + } + else + if (fineCorrector < -params.carrierDiff / 2) { + coarseCorrector -= params.carrierDiff; + fineCorrector += params.carrierDiff; + } + //ReadyForNewFrame: + /// and off we go, up to the next frame + PROFILE_FRAME_DECODED(); + goto SyncOnPhase; + } + catch (const NotRunningAnymore&) { + //std::clog << "OFDM-processor: closing down" << std::endl; + } + catch (const InputFailure&) { + //std::clog << "OFDM-processor: input not ok, closing down" << std::endl; + running = false; //Needed before onInputFailure, because subsequent calls will call OFDMProcessor::stop() + radioInterface.onInputFailure(); + } + running = false; +} + +void OFDMProcessor::stop() +{ + if (running) { + running = false; + if (threadHandle.joinable()) { + threadHandle.join(); + } + } +} + +void OFDMProcessor::resetCoarseCorrector() +{ + coarseCorrector = 0; +} + +void OFDMProcessor::setReceiverOptions(const RadioReceiverOptions rro) +{ + std::unique_lock lock(receiver_options_mutex); + bool need_reset = (receiver_options.disableCoarseCorrector != rro.disableCoarseCorrector); + receiver_options = rro; + phaseRef.selectFFTWindowPlacement(rro.fftPlacementMethod); + lock.unlock(); + + if (need_reset) { + restart(); + } +} + +void OFDMProcessor::set_scanMode(bool b) +{ + scanMode = b; +} + +#define RANGE 36 +int16_t OFDMProcessor::processPRS(DSPCOMPLEX *v, const FreqsyncMethod& freqsyncMethod) +{ + int16_t i, j, index = 100; + + memcpy(fft_buffer, v, T_u * sizeof(DSPCOMPLEX)); + fft_handler.do_FFT(); + + switch (freqsyncMethod) { + case FreqsyncMethod::GetMiddle: + return getMiddle(fft_buffer); + case FreqsyncMethod::CorrelatePRS: + { + // The "best" approach for computing the coarse frequency + // offset is to look at the spectrum of symbol 0 and relate that + // with the spectrum as it should be, i.e. the refTable + // However, since there might be + // a pretty large phase offset between the incoming data and + // the reference table data, we correlate the + // phase differences between the subsequent carriers rather + // than the values in the segments themselves. + // It seems to work pretty well + // + // The phase differences are computed once + for (i = 0; i < SEARCH_RANGE + CORRELATION_LENGTH; i ++) { + int16_t baseIndex = T_u - SEARCH_RANGE / 2 + i; + correlationVector[i] = + arg(fft_buffer[baseIndex % T_u] * + conj(fft_buffer[(baseIndex + 1) % T_u])); + } + + float MMax = 0; + for (i = 0; i < SEARCH_RANGE; i ++) { + float sum = 0; + for (j = 0; j < CORRELATION_LENGTH; j ++) { + sum += abs(refArg [j] * correlationVector[i + j]); + if (sum > MMax) { + MMax = sum; + index = i; + } + } + } + + // Now map the index back to the right carrier + return T_u - SEARCH_RANGE / 2 + index - T_u; + } + case FreqsyncMethod::PatternOfZeros: + { + // An alternative way is to look at a special pattern consisting + // of zeros in the row of args between successive carriers. + float Mmin = 1000; + for (i = T_u - SEARCH_RANGE / 2; i < T_u + SEARCH_RANGE / 2; i ++) { + float a1 = abs (abs (arg (fft_buffer [(i + 1) % T_u] * + conj (fft_buffer [(i + 2) % T_u])) / M_PI) - 1); + float a2 = abs (abs (arg (fft_buffer [(i + 2) % T_u] * + conj (fft_buffer [(i + 3) % T_u])) / M_PI) - 1); + float a3 = abs (arg (fft_buffer [(i + 3) % T_u] * + conj (fft_buffer [(i + 4) % T_u]))); + float a4 = abs (arg (fft_buffer [(i + 4) % T_u] * + conj (fft_buffer [(i + 5) % T_u]))); + float a5 = abs (arg (fft_buffer [(i + 5) % T_u] * + conj (fft_buffer [(i + 6) % T_u]))); + float b1 = abs (abs (arg (fft_buffer [(i + 16 + 1) % T_u] * + conj (fft_buffer [(i + 16 + 3) % T_u])) / M_PI) - 1); + float b2 = abs (arg (fft_buffer [(i + 16 + 3) % T_u] * + conj (fft_buffer [(i + 16 + 4) % T_u]))); + float b3 = abs (arg (fft_buffer [(i + 16 + 4) % T_u] * + conj (fft_buffer [(i + 16 + 5) % T_u]))); + float b4 = abs (arg (fft_buffer [(i + 16 + 5) % T_u] * + conj (fft_buffer [(i + 16 + 6) % T_u]))); + float sum = a1 + a2 + a3 + a4 + a5 + b1 + b2 + b3 + b4; + if (sum < Mmin) { + Mmin = sum; + index = i; + } + } + return index - T_u; + } + } + throw std::logic_error("Unimplemented freqsyncMethod"); +} + +int16_t OFDMProcessor::getMiddle (DSPCOMPLEX *v) +{ + int16_t i; + DSPFLOAT sum = 0; + int16_t maxIndex = 0; + DSPFLOAT oldMax = 0; + // + // basic sum over K carriers that are - most likely - + // in the range + // The range in which the carrier should be is + // T_u / 2 - K / 2 .. T_u / 2 + K / 2 + // We first determine an initial sum over params.K carriers + for (i = 40; i < params.K + 40; i ++) + sum += abs (v [(T_u / 2 + i) % T_u]); + // + // Now a moving sum, look for a maximum within a reasonable + // range (around (T_u - K) / 2, the start of the useful frequencies) + for (i = 40; i < T_u - (params.K - 40); i ++) { + sum -= abs (v [(T_u / 2 + i) % T_u]); + sum += abs (v [(T_u / 2 + i + params.K) % T_u]); + if (sum > oldMax) { + sum = oldMax; + maxIndex = i; + } + } + return maxIndex - (T_u - params.K) / 2; +} diff --git a/src/dabdsp/ofdm-processor.h b/src/dabdsp/ofdm-processor.h index 85e1496..f0ef5b3 100644 --- a/src/dabdsp/ofdm-processor.h +++ b/src/dabdsp/ofdm-processor.h @@ -1,124 +1,124 @@ -/* - * Copyright (C) 2018 - * Matthias P. Braendli (matthias.braendli@mpb.li) - * - * Copyright (C) 2017 - * Albrecht Lohofener (albrechtloh@gmx.de) - * - * This file is based on SDR-J - * Copyright (C) 2010, 2011, 2012, 2013 - * Jan van Katwijk (J.vanKatwijk@gmail.com) - * - * This file is part of the welle.io. - * Many of the ideas as implemented in welle.io are derived from - * other work, made available through the GNU general Public License. - * All copyrights of the original authors are recognized. - * - * welle.io is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * welle.io 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with welle.io; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - -#ifndef __OFDM_PROCESSOR__ -#define __OFDM_PROCESSOR__ - -#include "dab-constants.h" -#include -#include -#include -#include -#include "phasereference.h" -#include "ofdm-decoder.h" -#include "tii-decoder.h" -#include "fft.h" -#include "radio-controller.h" -#include "radio-receiver-options.h" -#include "fic-handler.h" -#include "msc-handler.h" - -class OFDMProcessor -{ -// Identifier "interface" is already defined in the w32api header basetype.h - public: - OFDMProcessor(InputInterface& inputInterface, - const DABParams& params, - RadioControllerInterface& ri, - MscHandler& msc, - FicHandler& fic, - RadioReceiverOptions rro); - ~OFDMProcessor(); - - /* Start or restart the OFDMProcessor */ - void restart(); - - void stop(); - void resetCoarseCorrector(); - void setReceiverOptions(const RadioReceiverOptions rro); - void set_scanMode(bool); - - private: - std::mutex receiver_options_mutex; - RadioReceiverOptions receiver_options; - - std::thread threadHandle; - int32_t syncBufferIndex = 0; - RadioControllerInterface& radioInterface; - InputInterface& input; - const DABParams& params; - FicHandler& ficHandler; - std::vector impulseResponseBuffer; - TIIDecoder tiiDecoder; - - std::atomic running = ATOMIC_VAR_INIT(false); - - int32_t T_null; - int32_t T_u; - int32_t T_s; - int32_t T_F; - int32_t coarseSyncCounter = 0; - - std::vector oscillatorTable; - - int32_t localPhase = 0; - - float sLevel = 0; - int32_t sampleCnt = 0; - - int16_t lastValidFineCorrector = 0; - int32_t lastValidCoarseCorrector = 0; - int16_t fineCorrector = 0; - int32_t coarseCorrector = 0; - - uint32_t ofdmBufferIndex = 0; - PhaseReference phaseRef; - OfdmDecoder ofdmDecoder; - std::vector correlationVector; - std::vector refArg; - - bool scanMode = false; - int attempts = 0; - - int32_t bufferContent = 0; - - fft::Forward fft_handler; - DSPCOMPLEX *fft_buffer; // of size T_u - - DSPCOMPLEX getSample(int32_t); - void getSamples(DSPCOMPLEX *, int16_t, int32_t); - void run(void); - int16_t processPRS(DSPCOMPLEX *v, const FreqsyncMethod& freqsyncMethod); - int16_t getMiddle(DSPCOMPLEX *); -}; -#endif - +/* + * Copyright (C) 2018 + * Matthias P. Braendli (matthias.braendli@mpb.li) + * + * Copyright (C) 2017 + * Albrecht Lohofener (albrechtloh@gmx.de) + * + * This file is based on SDR-J + * Copyright (C) 2010, 2011, 2012, 2013 + * Jan van Katwijk (J.vanKatwijk@gmail.com) + * + * This file is part of the welle.io. + * Many of the ideas as implemented in welle.io are derived from + * other work, made available through the GNU general Public License. + * All copyrights of the original authors are recognized. + * + * welle.io is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * welle.io 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with welle.io; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef __OFDM_PROCESSOR__ +#define __OFDM_PROCESSOR__ + +#include "dab-constants.h" +#include +#include +#include +#include +#include "phasereference.h" +#include "ofdm-decoder.h" +#include "tii-decoder.h" +#include "fft.h" +#include "radio-controller.h" +#include "radio-receiver-options.h" +#include "fic-handler.h" +#include "msc-handler.h" + +class OFDMProcessor +{ +// Identifier "interface" is already defined in the w32api header basetype.h + public: + OFDMProcessor(InputInterface& inputInterface, + const DABParams& params, + RadioControllerInterface& ri, + MscHandler& msc, + FicHandler& fic, + RadioReceiverOptions rro); + ~OFDMProcessor(); + + /* Start or restart the OFDMProcessor */ + void restart(); + + void stop(); + void resetCoarseCorrector(); + void setReceiverOptions(const RadioReceiverOptions rro); + void set_scanMode(bool); + + private: + std::mutex receiver_options_mutex; + RadioReceiverOptions receiver_options; + + std::thread threadHandle; + int32_t syncBufferIndex = 0; + RadioControllerInterface& radioInterface; + InputInterface& input; + const DABParams& params; + FicHandler& ficHandler; + std::vector impulseResponseBuffer; + TIIDecoder tiiDecoder; + + std::atomic running = ATOMIC_VAR_INIT(false); + + int32_t T_null; + int32_t T_u; + int32_t T_s; + int32_t T_F; + int32_t coarseSyncCounter = 0; + + std::vector oscillatorTable; + + int32_t localPhase = 0; + + float sLevel = 0; + int32_t sampleCnt = 0; + + int16_t lastValidFineCorrector = 0; + int32_t lastValidCoarseCorrector = 0; + int16_t fineCorrector = 0; + int32_t coarseCorrector = 0; + + uint32_t ofdmBufferIndex = 0; + PhaseReference phaseRef; + OfdmDecoder ofdmDecoder; + std::vector correlationVector; + std::vector refArg; + + bool scanMode = false; + int attempts = 0; + + int32_t bufferContent = 0; + + fft::Forward fft_handler; + DSPCOMPLEX *fft_buffer; // of size T_u + + DSPCOMPLEX getSample(int32_t); + void getSamples(DSPCOMPLEX *, int16_t, int32_t); + void run(void); + int16_t processPRS(DSPCOMPLEX *v, const FreqsyncMethod& freqsyncMethod); + int16_t getMiddle(DSPCOMPLEX *); +}; +#endif + diff --git a/src/dabdsp/phasereference.cpp b/src/dabdsp/phasereference.cpp index 218bea3..e87fdd2 100644 --- a/src/dabdsp/phasereference.cpp +++ b/src/dabdsp/phasereference.cpp @@ -1,254 +1,254 @@ -/* - * Copyright (C) 2013 - * Jan van Katwijk (J.vanKatwijk@gmail.com) - * Lazy Chair Programming - * - * This file is part of the SDR-J (JSDR). - * SDR-J is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * SDR-J 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with SDR-J; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ -#include "phasereference.h" -#include "string.h" -#include -#include -#include -/** - * \class phaseReference - * Implements the correlation that is used to identify - * the "first" element (following the cyclic prefix) of - * the first non-null block of a frame - * The class inherits from the phaseTable. - */ -PhaseReference::PhaseReference(const DABParams& p, FFTPlacementMethod fft_placement_method) : - PhaseTable(p.dabMode), - fft_placement(fft_placement_method), - fft_processor(p.T_u), - res_processor(p.T_u) -{ - DSPFLOAT phi_k; - - refTable.resize(p.T_u); - fft_buffer = fft_processor.getVector(); - res_buffer = res_processor.getVector(); - - for (int i = 1; i <= p.K / 2; i ++) { - phi_k = get_Phi(i); - refTable[i] = DSPCOMPLEX(cos(phi_k), sin(phi_k)); - - phi_k = get_Phi(-i); - refTable[p.T_u - i] = DSPCOMPLEX(cos(phi_k), sin(phi_k)); - } -} - -DSPCOMPLEX PhaseReference::operator[](size_t ix) -{ - return refTable.at(ix); -} - -void PhaseReference::selectFFTWindowPlacement(FFTPlacementMethod new_fft_placement) -{ - fft_placement = new_fft_placement; -} - -/** - * \brief findIndex - * the vector v contains "Tu" samples that are believed to - * belong to the first non-null block of a DAB frame. - * We correlate the data in this vector with the predefined - * data, and if the maximum exceeds a threshold value, - * we believe that that indicates the first sample we were - * looking for. - */ -int32_t PhaseReference::findIndex(DSPCOMPLEX *v, - std::vector& impulseResponseBuffer) -{ - int32_t maxIndex = -1; - float sum = 0; - - size_t Tu = refTable.size(); - - memcpy(fft_buffer, v, Tu * sizeof(DSPCOMPLEX)); - - fft_processor.do_FFT(); - - // back into the frequency domain, now correlate - for (size_t i = 0; i < Tu; i++) - res_buffer[i] = fft_buffer[i] * conj(refTable[i]); - - // and, again, back into the time domain - res_processor.do_IFFT(); - - impulseResponseBuffer.resize(Tu); - - switch (fft_placement) { - case FFTPlacementMethod::StrongestPeak: - { - const float threshold = 3; - - /** - * We compute the average signal value ... - */ - for (size_t i = 0; i < Tu; i++) - sum += abs(res_buffer[i]); - - DSPFLOAT max = -10000; - for (size_t i = 0; i < Tu; i++) { - const float value = abs(res_buffer[i]); - impulseResponseBuffer[i] = value; - - if (value > max) { - maxIndex = i; - max = value; - } - } - /** - * that gives us a basis for defining the threshold - */ - if (max < threshold * sum / Tu) - return -std::abs(max * Tu / sum) - 1; - else - return maxIndex; - } - case FFTPlacementMethod::EarliestPeakWithBinning: - { - /* Calculate peaks over bins of 25 samples, keep the - * 4 bins with the highest peaks, take the index from the peak - * in the earliest bin, but not any earlier than 500 samples. - * - * Goal: avoid that the receiver locks onto the strongest peak - * in case earlier peaks are present. - * See https://tech.ebu.ch/docs/techreports/tr024.pdf part 2.4 - * for more details. - */ - - using namespace std; - - struct peak_t { - int index = -1; - float value = 0; - }; - - vector bins; - float mean = 0; - - constexpr int bin_size = 20; - constexpr size_t num_bins_to_keep = 4; - for (size_t i = 0; i + bin_size < Tu; i += bin_size) { - peak_t peak; - for (size_t j = 0; j < bin_size; j++) { - const float value = abs(res_buffer[i + j]); - mean += value; - impulseResponseBuffer[i + j] = value; - - if (value > peak.value) { - peak.value = value; - peak.index = i + j; - } - } - bins.push_back(move(peak)); - } - - mean /= Tu; - - - if (bins.size() < num_bins_to_keep) { - throw logic_error("Sync err, not enough bins"); - } - - // Sort bins by highest peak - sort(bins.begin(), bins.end(), - [&](const peak_t& lhs, const peak_t& rhs) { - return lhs.value > rhs.value; - }); - - // Keep only bins that are not too far from highest peak - const int peak_index = bins.front().index; - constexpr int max_subpeak_distance = 500; - bins.erase( - remove_if(bins.begin(), bins.end(), - [&](const peak_t& p) { - return abs(p.index - peak_index) > max_subpeak_distance; - }), bins.end()); - - if (bins.size() > num_bins_to_keep) { - bins.resize(num_bins_to_keep); - } - - const auto thresh = 3 * mean; - - // Keep only bins where the peak is above the threshold - bins.erase( - remove_if(bins.begin(), bins.end(), - [&](const peak_t& p) { - return p.value < thresh; - }), bins.end()); - - if (bins.empty()) { - return -1; - } - else { - // Take first bin - const auto earliest_bin = min_element(bins.begin(), bins.end(), - [&](const peak_t& lhs, const peak_t& rhs) { - return lhs.index < rhs.index; - }); - - return earliest_bin->index; - } - } - case FFTPlacementMethod::ThresholdBeforePeak: - { - using namespace std; - - for (size_t i = 0; i < Tu; i++) { - const float v = abs(res_buffer[i]); - impulseResponseBuffer[i] = v; - sum += v; - } - - const size_t windowsize = 100; - - vector peak_averages(Tu); - float global_max = -10000; - for (size_t i = 0; i + windowsize < Tu; i++) { - float max = -10000; - for (size_t j = 0; j < windowsize; j++) { - const float value = impulseResponseBuffer[i + j]; - - if (value > max) { - max = value; - } - } - peak_averages[i] = max; - - if (max > global_max) { - global_max = max; - } - } - - // First verify that there is a peak - const float required_peak_over_average = 3; - if (global_max > required_peak_over_average * sum / Tu) { - const float thresh = global_max / 2; - for (size_t i = 0; i + windowsize < Tu; i++) { - if (peak_averages[i + windowsize] > thresh) { - return i; - } - } - } - return -1; - } - } - throw std::logic_error("Unhandled FFTPlacementMethod"); -} +/* + * Copyright (C) 2013 + * Jan van Katwijk (J.vanKatwijk@gmail.com) + * Lazy Chair Programming + * + * This file is part of the SDR-J (JSDR). + * SDR-J is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * SDR-J 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with SDR-J; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "phasereference.h" +#include "string.h" +#include +#include +#include +/** + * \class phaseReference + * Implements the correlation that is used to identify + * the "first" element (following the cyclic prefix) of + * the first non-null block of a frame + * The class inherits from the phaseTable. + */ +PhaseReference::PhaseReference(const DABParams& p, FFTPlacementMethod fft_placement_method) : + PhaseTable(p.dabMode), + fft_placement(fft_placement_method), + fft_processor(p.T_u), + res_processor(p.T_u) +{ + DSPFLOAT phi_k; + + refTable.resize(p.T_u); + fft_buffer = fft_processor.getVector(); + res_buffer = res_processor.getVector(); + + for (int i = 1; i <= p.K / 2; i ++) { + phi_k = get_Phi(i); + refTable[i] = DSPCOMPLEX(cos(phi_k), sin(phi_k)); + + phi_k = get_Phi(-i); + refTable[p.T_u - i] = DSPCOMPLEX(cos(phi_k), sin(phi_k)); + } +} + +DSPCOMPLEX PhaseReference::operator[](size_t ix) +{ + return refTable.at(ix); +} + +void PhaseReference::selectFFTWindowPlacement(FFTPlacementMethod new_fft_placement) +{ + fft_placement = new_fft_placement; +} + +/** + * \brief findIndex + * the vector v contains "Tu" samples that are believed to + * belong to the first non-null block of a DAB frame. + * We correlate the data in this vector with the predefined + * data, and if the maximum exceeds a threshold value, + * we believe that that indicates the first sample we were + * looking for. + */ +int32_t PhaseReference::findIndex(DSPCOMPLEX *v, + std::vector& impulseResponseBuffer) +{ + int32_t maxIndex = -1; + float sum = 0; + + size_t Tu = refTable.size(); + + memcpy(fft_buffer, v, Tu * sizeof(DSPCOMPLEX)); + + fft_processor.do_FFT(); + + // back into the frequency domain, now correlate + for (size_t i = 0; i < Tu; i++) + res_buffer[i] = fft_buffer[i] * conj(refTable[i]); + + // and, again, back into the time domain + res_processor.do_IFFT(); + + impulseResponseBuffer.resize(Tu); + + switch (fft_placement) { + case FFTPlacementMethod::StrongestPeak: + { + const float threshold = 3; + + /** + * We compute the average signal value ... + */ + for (size_t i = 0; i < Tu; i++) + sum += abs(res_buffer[i]); + + DSPFLOAT max = -10000; + for (size_t i = 0; i < Tu; i++) { + const float value = abs(res_buffer[i]); + impulseResponseBuffer[i] = value; + + if (value > max) { + maxIndex = i; + max = value; + } + } + /** + * that gives us a basis for defining the threshold + */ + if (max < threshold * sum / Tu) + return -std::abs(max * Tu / sum) - 1; + else + return maxIndex; + } + case FFTPlacementMethod::EarliestPeakWithBinning: + { + /* Calculate peaks over bins of 25 samples, keep the + * 4 bins with the highest peaks, take the index from the peak + * in the earliest bin, but not any earlier than 500 samples. + * + * Goal: avoid that the receiver locks onto the strongest peak + * in case earlier peaks are present. + * See https://tech.ebu.ch/docs/techreports/tr024.pdf part 2.4 + * for more details. + */ + + using namespace std; + + struct peak_t { + int index = -1; + float value = 0; + }; + + vector bins; + float mean = 0; + + constexpr int bin_size = 20; + constexpr size_t num_bins_to_keep = 4; + for (size_t i = 0; i + bin_size < Tu; i += bin_size) { + peak_t peak; + for (size_t j = 0; j < bin_size; j++) { + const float value = abs(res_buffer[i + j]); + mean += value; + impulseResponseBuffer[i + j] = value; + + if (value > peak.value) { + peak.value = value; + peak.index = i + j; + } + } + bins.push_back(move(peak)); + } + + mean /= Tu; + + + if (bins.size() < num_bins_to_keep) { + throw logic_error("Sync err, not enough bins"); + } + + // Sort bins by highest peak + sort(bins.begin(), bins.end(), + [&](const peak_t& lhs, const peak_t& rhs) { + return lhs.value > rhs.value; + }); + + // Keep only bins that are not too far from highest peak + const int peak_index = bins.front().index; + constexpr int max_subpeak_distance = 500; + bins.erase( + remove_if(bins.begin(), bins.end(), + [&](const peak_t& p) { + return abs(p.index - peak_index) > max_subpeak_distance; + }), bins.end()); + + if (bins.size() > num_bins_to_keep) { + bins.resize(num_bins_to_keep); + } + + const auto thresh = 3 * mean; + + // Keep only bins where the peak is above the threshold + bins.erase( + remove_if(bins.begin(), bins.end(), + [&](const peak_t& p) { + return p.value < thresh; + }), bins.end()); + + if (bins.empty()) { + return -1; + } + else { + // Take first bin + const auto earliest_bin = min_element(bins.begin(), bins.end(), + [&](const peak_t& lhs, const peak_t& rhs) { + return lhs.index < rhs.index; + }); + + return earliest_bin->index; + } + } + case FFTPlacementMethod::ThresholdBeforePeak: + { + using namespace std; + + for (size_t i = 0; i < Tu; i++) { + const float v = abs(res_buffer[i]); + impulseResponseBuffer[i] = v; + sum += v; + } + + const size_t windowsize = 100; + + vector peak_averages(Tu); + float global_max = -10000; + for (size_t i = 0; i + windowsize < Tu; i++) { + float max = -10000; + for (size_t j = 0; j < windowsize; j++) { + const float value = impulseResponseBuffer[i + j]; + + if (value > max) { + max = value; + } + } + peak_averages[i] = max; + + if (max > global_max) { + global_max = max; + } + } + + // First verify that there is a peak + const float required_peak_over_average = 3; + if (global_max > required_peak_over_average * sum / Tu) { + const float thresh = global_max / 2; + for (size_t i = 0; i + windowsize < Tu; i++) { + if (peak_averages[i + windowsize] > thresh) { + return i; + } + } + } + return -1; + } + } + throw std::logic_error("Unhandled FFTPlacementMethod"); +} diff --git a/src/dabdsp/phasereference.h b/src/dabdsp/phasereference.h index 2d65c61..0032996 100644 --- a/src/dabdsp/phasereference.h +++ b/src/dabdsp/phasereference.h @@ -1,58 +1,58 @@ -/* - * - * Copyright (C) 2013 - * Jan van Katwijk (J.vanKatwijk@gmail.com) - * Lazy Chair Programming - * - * This file is part of the SDR-J (JSDR). - * SDR-J is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * SDR-J 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with SDR-J; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ -#ifndef __PHASEREFERENCE -#define __PHASEREFERENCE - -#include "fft.h" -#include -#include -#include -#include -#include "phasetable.h" -#include "dab-constants.h" -#include "radio-receiver-options.h" - -class PhaseReference : public PhaseTable -{ - public: - PhaseReference(const DABParams& p, FFTPlacementMethod fft_placement_method); - int32_t findIndex(DSPCOMPLEX *v, - std::vector& impulseResponseBuffer); - - DSPCOMPLEX operator[](size_t ix); - - void selectFFTWindowPlacement(FFTPlacementMethod new_fft_placement); - - private: - std::vector refTable; - - FFTPlacementMethod fft_placement; - - fft::Forward fft_processor; - DSPCOMPLEX *fft_buffer; - - fft::Backward res_processor; - DSPCOMPLEX *res_buffer; -}; -#endif - +/* + * + * Copyright (C) 2013 + * Jan van Katwijk (J.vanKatwijk@gmail.com) + * Lazy Chair Programming + * + * This file is part of the SDR-J (JSDR). + * SDR-J is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * SDR-J 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with SDR-J; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __PHASEREFERENCE +#define __PHASEREFERENCE + +#include "fft.h" +#include +#include +#include +#include +#include "phasetable.h" +#include "dab-constants.h" +#include "radio-receiver-options.h" + +class PhaseReference : public PhaseTable +{ + public: + PhaseReference(const DABParams& p, FFTPlacementMethod fft_placement_method); + int32_t findIndex(DSPCOMPLEX *v, + std::vector& impulseResponseBuffer); + + DSPCOMPLEX operator[](size_t ix); + + void selectFFTWindowPlacement(FFTPlacementMethod new_fft_placement); + + private: + std::vector refTable; + + FFTPlacementMethod fft_placement; + + fft::Forward fft_processor; + DSPCOMPLEX *fft_buffer; + + fft::Backward res_processor; + DSPCOMPLEX *res_buffer; +}; +#endif + diff --git a/src/dabdsp/phasetable.cpp b/src/dabdsp/phasetable.cpp index f4fb61a..aa96320 100644 --- a/src/dabdsp/phasetable.cpp +++ b/src/dabdsp/phasetable.cpp @@ -1,189 +1,189 @@ -/* - * - * Copyright (C) 2013 - * Jan van Katwijk (J.vanKatwijk@gmail.com) - * Lazy Chair Programming - * - * This file is part of the SDR-J (JSDR). - * SDR-J is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * SDR-J 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with SDR-J; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ -#include "phasetable.h" - -#ifdef _WINDOWS -#define _USE_MATH_DEFINES -#include -#endif - -static const PhasetableElement modeI_table[] = { - {-768, -737, 0, 1}, - {-736, -705, 1, 2}, - {-704, -673, 2, 0}, - {-672, -641, 3, 1}, - {-640, -609, 0, 3}, - {-608, -577, 1, 2}, - {-576, -545, 2, 2}, - {-544, -513, 3, 3}, - {-512, -481, 0, 2}, - {-480, -449, 1, 1}, - {-448, -417, 2, 2}, - {-416, -385, 3, 3}, - {-384, -353, 0, 1}, - {-352, -321, 1, 2}, - {-320, -289, 2, 3}, - {-288, -257, 3, 3}, - {-256, -225, 0, 2}, - {-224, -193, 1, 2}, - {-192, -161, 2, 2}, - {-160, -129, 3, 1}, - {-128, -97, 0, 1}, - {-96, -65, 1, 3}, - {-64, -33, 2, 1}, - {-32, -1, 3, 2}, - { 1, 32, 0, 3}, - { 33, 64, 3, 1}, - { 65, 96, 2, 1}, - // { 97, 128, 2, 1}, found bug 2014-09-03 Jorgen Scott - { 97, 128, 1, 1}, - { 129, 160, 0, 2}, - { 161, 192, 3, 2}, - { 193, 224, 2, 1}, - { 225, 256, 1, 0}, - { 257, 288, 0, 2}, - { 289, 320, 3, 2}, - { 321, 352, 2, 3}, - { 353, 384, 1, 3}, - { 385, 416, 0, 0}, - { 417, 448, 3, 2}, - { 449, 480, 2, 1}, - { 481, 512, 1, 3}, - { 513, 544, 0, 3}, - { 545, 576, 3, 3}, - { 577, 608, 2, 3}, - { 609, 640, 1, 0}, - { 641, 672, 0, 3}, - { 673, 704, 3, 0}, - { 705, 736, 2, 1}, - { 737, 768, 1, 1}, - { -1000, -1000, 0, 0} -}; - -static const PhasetableElement modeII_table[] = { - {-192, -161, 0, 2}, - {-160, -129, 1, 3}, - {-128, -97, 2, 2}, - {-96, -65, 3, 2}, - {-64, -33, 0, 1}, - {-32, -1, 1, 2}, - {1, 32, 2, 0}, - {33, 64, 1, 2}, - {65, 96, 0, 2}, - {97, 128, 3, 1}, - {129, 160, 2, 0}, - {161, 192, 1, 3}, - {-1000, -1000, 0, 0} -}; - -static const PhasetableElement modeIV_table[] = { - {-384, -353, 0, 0}, - {-352, -321, 1, 1}, - {-320, -289, 2, 1}, - {-288, -257, 3, 2}, - {-256, -225, 0, 2}, - {-224, -193, 1, 2}, - {-192, -161, 2, 0}, - {-160, -129, 3, 3}, - {-128, -97, 0, 3}, - {-96, -65, 1, 1}, - {-64, -33, 2, 3}, - {-32, -1, 3, 2}, - { 1, 32, 0, 0}, - { 33, 64, 3, 1}, - { 65, 96, 2, 0}, - { 97, 128, 1, 2}, - { 129, 160, 0, 0}, - { 161, 192, 3, 1}, - { 193, 224, 2, 2}, - { 225, 256, 1, 2}, - { 257, 288, 0, 2}, - { 289, 320, 3, 1}, - { 321, 352, 2, 3}, - { 353, 384, 1, 0}, - {-1000, -1000, 0, 0} -}; - -PhaseTable::PhaseTable(int16_t transmission_mode) : - mode(transmission_mode) -{ - switch (mode) { - case 1: - currentTable = modeI_table; - break; - case 2: - currentTable = modeII_table; - break; - case 4: - currentTable = modeIV_table; - break; - default: - throw std::runtime_error("Invalid mode selected"); - } -} - - -static const int8_t h0[] = { - 0, 2, 0, 0, 0, 0, 1, 1, 2, 0, 0, 0, 2, 2, 1, 1, - 0, 2, 0, 0, 0, 0, 1, 1, 2, 0, 0, 0, 2, 2, 1, 1}; - -static const int8_t h1[] = { - 0, 3, 2, 3, 0, 1, 3, 0, 2, 1, 2, 3, 2, 3, 3, 0, - 0, 3, 2, 3, 0, 1, 3, 0, 2, 1, 2, 3, 2, 3, 3, 0}; - -static const int8_t h2[] = { - 0, 0, 0, 2, 0, 2, 1, 3, 2, 2, 0, 2, 2, 0, 1, 3, - 0, 0, 0, 2, 0, 2, 1, 3, 2, 2, 0, 2, 2, 0, 1, 3}; - -static const int8_t h3[] = { - 0, 1, 2, 1, 0, 3, 3, 2, 2, 3, 2, 1, 2, 1, 3, 2, - 0, 1, 2, 1, 0, 3, 3, 2, 2, 3, 2, 1, 2, 1, 3, 2}; - -int32_t PhaseTable::h_table(int32_t i, int32_t j) -{ - switch (i) { - case 0: - return h0[j]; - case 1: - return h1[j]; - case 2: - return h2[j]; - case 3: - return h3[j]; - default: - throw std::logic_error("Invalid i in h_table"); - } -} - -DSPFLOAT PhaseTable::get_Phi(int32_t k) -{ - for (int j = 0; currentTable[j].kmin != -1000; j++) { - if ((currentTable[j].kmin <= k) && (k <= currentTable[j].kmax)) { - int k_prime = currentTable[j].kmin; - int i = currentTable[j].i; - int n = currentTable[j].n; - return M_PI / 2.0f * (h_table(i, k - k_prime) + n); - } - } - throw std::logic_error("Invalid k in get_Phi"); -} - +/* + * + * Copyright (C) 2013 + * Jan van Katwijk (J.vanKatwijk@gmail.com) + * Lazy Chair Programming + * + * This file is part of the SDR-J (JSDR). + * SDR-J is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * SDR-J 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with SDR-J; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "phasetable.h" + +#ifdef _WINDOWS +#define _USE_MATH_DEFINES +#include +#endif + +static const PhasetableElement modeI_table[] = { + {-768, -737, 0, 1}, + {-736, -705, 1, 2}, + {-704, -673, 2, 0}, + {-672, -641, 3, 1}, + {-640, -609, 0, 3}, + {-608, -577, 1, 2}, + {-576, -545, 2, 2}, + {-544, -513, 3, 3}, + {-512, -481, 0, 2}, + {-480, -449, 1, 1}, + {-448, -417, 2, 2}, + {-416, -385, 3, 3}, + {-384, -353, 0, 1}, + {-352, -321, 1, 2}, + {-320, -289, 2, 3}, + {-288, -257, 3, 3}, + {-256, -225, 0, 2}, + {-224, -193, 1, 2}, + {-192, -161, 2, 2}, + {-160, -129, 3, 1}, + {-128, -97, 0, 1}, + {-96, -65, 1, 3}, + {-64, -33, 2, 1}, + {-32, -1, 3, 2}, + { 1, 32, 0, 3}, + { 33, 64, 3, 1}, + { 65, 96, 2, 1}, + // { 97, 128, 2, 1}, found bug 2014-09-03 Jorgen Scott + { 97, 128, 1, 1}, + { 129, 160, 0, 2}, + { 161, 192, 3, 2}, + { 193, 224, 2, 1}, + { 225, 256, 1, 0}, + { 257, 288, 0, 2}, + { 289, 320, 3, 2}, + { 321, 352, 2, 3}, + { 353, 384, 1, 3}, + { 385, 416, 0, 0}, + { 417, 448, 3, 2}, + { 449, 480, 2, 1}, + { 481, 512, 1, 3}, + { 513, 544, 0, 3}, + { 545, 576, 3, 3}, + { 577, 608, 2, 3}, + { 609, 640, 1, 0}, + { 641, 672, 0, 3}, + { 673, 704, 3, 0}, + { 705, 736, 2, 1}, + { 737, 768, 1, 1}, + { -1000, -1000, 0, 0} +}; + +static const PhasetableElement modeII_table[] = { + {-192, -161, 0, 2}, + {-160, -129, 1, 3}, + {-128, -97, 2, 2}, + {-96, -65, 3, 2}, + {-64, -33, 0, 1}, + {-32, -1, 1, 2}, + {1, 32, 2, 0}, + {33, 64, 1, 2}, + {65, 96, 0, 2}, + {97, 128, 3, 1}, + {129, 160, 2, 0}, + {161, 192, 1, 3}, + {-1000, -1000, 0, 0} +}; + +static const PhasetableElement modeIV_table[] = { + {-384, -353, 0, 0}, + {-352, -321, 1, 1}, + {-320, -289, 2, 1}, + {-288, -257, 3, 2}, + {-256, -225, 0, 2}, + {-224, -193, 1, 2}, + {-192, -161, 2, 0}, + {-160, -129, 3, 3}, + {-128, -97, 0, 3}, + {-96, -65, 1, 1}, + {-64, -33, 2, 3}, + {-32, -1, 3, 2}, + { 1, 32, 0, 0}, + { 33, 64, 3, 1}, + { 65, 96, 2, 0}, + { 97, 128, 1, 2}, + { 129, 160, 0, 0}, + { 161, 192, 3, 1}, + { 193, 224, 2, 2}, + { 225, 256, 1, 2}, + { 257, 288, 0, 2}, + { 289, 320, 3, 1}, + { 321, 352, 2, 3}, + { 353, 384, 1, 0}, + {-1000, -1000, 0, 0} +}; + +PhaseTable::PhaseTable(int16_t transmission_mode) : + mode(transmission_mode) +{ + switch (mode) { + case 1: + currentTable = modeI_table; + break; + case 2: + currentTable = modeII_table; + break; + case 4: + currentTable = modeIV_table; + break; + default: + throw std::runtime_error("Invalid mode selected"); + } +} + + +static const int8_t h0[] = { + 0, 2, 0, 0, 0, 0, 1, 1, 2, 0, 0, 0, 2, 2, 1, 1, + 0, 2, 0, 0, 0, 0, 1, 1, 2, 0, 0, 0, 2, 2, 1, 1}; + +static const int8_t h1[] = { + 0, 3, 2, 3, 0, 1, 3, 0, 2, 1, 2, 3, 2, 3, 3, 0, + 0, 3, 2, 3, 0, 1, 3, 0, 2, 1, 2, 3, 2, 3, 3, 0}; + +static const int8_t h2[] = { + 0, 0, 0, 2, 0, 2, 1, 3, 2, 2, 0, 2, 2, 0, 1, 3, + 0, 0, 0, 2, 0, 2, 1, 3, 2, 2, 0, 2, 2, 0, 1, 3}; + +static const int8_t h3[] = { + 0, 1, 2, 1, 0, 3, 3, 2, 2, 3, 2, 1, 2, 1, 3, 2, + 0, 1, 2, 1, 0, 3, 3, 2, 2, 3, 2, 1, 2, 1, 3, 2}; + +int32_t PhaseTable::h_table(int32_t i, int32_t j) +{ + switch (i) { + case 0: + return h0[j]; + case 1: + return h1[j]; + case 2: + return h2[j]; + case 3: + return h3[j]; + default: + throw std::logic_error("Invalid i in h_table"); + } +} + +DSPFLOAT PhaseTable::get_Phi(int32_t k) +{ + for (int j = 0; currentTable[j].kmin != -1000; j++) { + if ((currentTable[j].kmin <= k) && (k <= currentTable[j].kmax)) { + int k_prime = currentTable[j].kmin; + int i = currentTable[j].i; + int n = currentTable[j].n; + return M_PI / 2.0f * (h_table(i, k - k_prime) + n); + } + } + throw std::logic_error("Invalid k in get_Phi"); +} + diff --git a/src/dabdsp/phasetable.h b/src/dabdsp/phasetable.h index 3dafaf3..d7b4fcf 100644 --- a/src/dabdsp/phasetable.h +++ b/src/dabdsp/phasetable.h @@ -1,51 +1,51 @@ -/* - * - * Copyright (C) 2013 - * Jan van Katwijk (J.vanKatwijk@gmail.com) - * Lazy Chair Programming - * - * This file is part of the SDR-J (JSDR). - * SDR-J is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * SDR-J 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with SDR-J; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - -#ifndef __PHASE_TABLE -#define __PHASE_TABLE - -#include -#include -#include "dab-constants.h" - -struct PhasetableElement -{ - int32_t kmin, kmax; - int32_t i; - int32_t n; -}; - - -class PhaseTable -{ - public: - PhaseTable(int16_t transmission_mode); - DSPFLOAT get_Phi(int32_t); - - private: - const PhasetableElement *currentTable; - int16_t mode; - int32_t h_table(int32_t i, int32_t j); -}; -#endif - +/* + * + * Copyright (C) 2013 + * Jan van Katwijk (J.vanKatwijk@gmail.com) + * Lazy Chair Programming + * + * This file is part of the SDR-J (JSDR). + * SDR-J is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * SDR-J 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with SDR-J; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef __PHASE_TABLE +#define __PHASE_TABLE + +#include +#include +#include "dab-constants.h" + +struct PhasetableElement +{ + int32_t kmin, kmax; + int32_t i; + int32_t n; +}; + + +class PhaseTable +{ + public: + PhaseTable(int16_t transmission_mode); + DSPFLOAT get_Phi(int32_t); + + private: + const PhasetableElement *currentTable; + int16_t mode; + int32_t h_table(int32_t i, int32_t j); +}; +#endif + diff --git a/src/dabdsp/profiling.cpp b/src/dabdsp/profiling.cpp index f6fb42d..c382ef5 100644 --- a/src/dabdsp/profiling.cpp +++ b/src/dabdsp/profiling.cpp @@ -1,204 +1,204 @@ -/* - * Copyright (C) 2019 - * Matthias P. Braendli (matthias.braendli@mpb.li) - * - * This file is part of the welle.io. - * Many of the ideas as implemented in welle.io are derived from - * other work, made available through the GNU general Public License. - * All copyrights of the original authors are recognized. - * - * welle.io is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * welle.io 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with welle.io; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - -#if defined(WITH_PROFILING) -#include -#include -#include -#include -#include - -#include "various/profiling.h" - -using namespace std; - -static Profiler profiler; - -Profiler& get_profiler() { - return profiler; -} - -#define MARK_TO_CSTR_CASE(m) case ProfilingMark::m: return #m; -const char* mark_to_cstr(const ProfilingMark& m) { - switch (m) { - MARK_TO_CSTR_CASE(NotSynced) - MARK_TO_CSTR_CASE(SyncOnEndNull) - MARK_TO_CSTR_CASE(SyncOnPhase) - MARK_TO_CSTR_CASE(FindIndex) - MARK_TO_CSTR_CASE(DataSymbols) - MARK_TO_CSTR_CASE(PushAllSymbols) - MARK_TO_CSTR_CASE(OnNewNull) - MARK_TO_CSTR_CASE(DecodeTII) - - MARK_TO_CSTR_CASE(ProcessPRS) - MARK_TO_CSTR_CASE(ProcessSymbol) - MARK_TO_CSTR_CASE(Deinterleaver) - MARK_TO_CSTR_CASE(FICHandler) - MARK_TO_CSTR_CASE(MSCHandler) - MARK_TO_CSTR_CASE(SymbolProcessed) - - MARK_TO_CSTR_CASE(DAGetMSCData) - MARK_TO_CSTR_CASE(DADeinterleave) - MARK_TO_CSTR_CASE(DADeconvolve) - MARK_TO_CSTR_CASE(DADispersal) - MARK_TO_CSTR_CASE(DADecode) - MARK_TO_CSTR_CASE(DADone) - } - - return "unknown"; -} - -Profiler::Profiler() { - clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &startup_time_cputime); - clock_gettime(CLOCK_MONOTONIC, &startup_time_monotonic); -} - -struct timespec& operator+=(struct timespec& t1, const struct timespec& t2) { - t1.tv_sec += t2.tv_sec; - t1.tv_nsec += t2.tv_nsec; - - if (t1.tv_nsec >= 1000000000ll) { - t1.tv_nsec -= 1000000000ll; - t1.tv_sec += 1; - } - - return t1; -} - -struct timespec operator-(struct timespec t1, struct timespec t2) { - struct timespec t; - - if (t1.tv_sec < t2.tv_sec) { - cerr << "******************* Timestamp difference negative" << endl; - t.tv_sec = 0; - t.tv_nsec = 0; - return t; - } - - t.tv_sec = t1.tv_sec - t2.tv_sec; - - if (t1.tv_nsec >= t2.tv_nsec) { - t.tv_nsec = t1.tv_nsec - t2.tv_nsec; - } - else { - t.tv_sec -= 1; - t.tv_nsec = 1000000000ll + t1.tv_nsec - t2.tv_nsec; - } - return t; -} - -std::ostream& operator<<(std::ostream& out, const struct timespec& ts) -{ - char nanos[32]; - snprintf(nanos, 31, "%09ld", ts.tv_nsec); - return out << ts.tv_sec << "." << nanos; -} - -Profiler::~Profiler() { - struct timespec stop_time_cputime; - struct timespec stop_time_monotonic; - clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &stop_time_cputime); - clock_gettime(CLOCK_MONOTONIC, &stop_time_monotonic); - - ofstream dump("profiling_points.csv"); - dump << "thread_id,mark,time_sec,time_ns" << endl; - for (const auto tp: m_timepoints) { - for (const auto points : tp.second) { - dump << tp.first << "," << - mark_to_cstr(points.p) << "," << - points.timestamp.tv_sec << "," << - points.timestamp.tv_nsec << endl; - } - } - - ofstream profiling("profiling_stats.csv"); - profiling << "cputime,start," << startup_time_cputime << endl; - profiling << "cputime,stop," << stop_time_cputime << endl; - profiling << "monotonic,start," << startup_time_monotonic << endl; - profiling << "monotonic,stop," << stop_time_monotonic << endl; - profiling << "cputime,diff," << stop_time_cputime - startup_time_cputime << endl; - profiling << "monotonic,diff," << stop_time_monotonic - startup_time_monotonic << endl; - profiling << "frames,decoded," << num_frames_decoded << endl; - - // See http://www.graphviz.org/documentation/ - ofstream graph("profiling.dot"); - - graph << "digraph G { " << endl; - - size_t count = 0; - - for (const auto tp: m_timepoints) { - if (tp.second.size() < 2) { - continue; - } - - graph << "subgraph cluster_" << tp.first << " { " << endl; - graph << "colorscheme=\"gnbu8\";" << endl; - graph << "bgcolor=" << (count % 8) + 1 << ";" << endl; - count++; - - map, struct timespec> from_to_times; - - for (auto it = tp.second.begin(); next(it) != tp.second.end(); ++it) { - from_to_times[make_pair(it->p, next(it)->p)] += next(it)->timestamp - it->timestamp; - } - - double maxw = 0; - for (auto& d : from_to_times) { - double w = log10(1 + d.second.tv_sec * 1000 + d.second.tv_nsec / 1000000); - if (w > maxw) maxw = w; - } - - for (auto& d : from_to_times) { - int w = (d.second.tv_sec * 1000 + d.second.tv_nsec / 1000000); - - char color[16]; - snprintf(color, 15, "#%02x%02x%02x", (int)(255 * log10(w+1)/maxw), 0, 0); - - graph << mark_to_cstr(d.first.first) << " -> " << mark_to_cstr(d.first.second) << - " [color=\"" << color << "\"" - " label=\"" << w << "ms\"" - "];" << endl; - } - graph << "}" << endl; - } - graph << "}" << endl; -} - -void Profiler::save_time(const ProfilingMark m) { - const auto id = this_thread::get_id(); - - struct timespec now; - clock_gettime(CLOCK_THREAD_CPUTIME_ID, &now); - - lock_guard lock(m_mutex); - m_timepoints[id].emplace_back(now, m); -} - -void Profiler::frame_decoded() { - num_frames_decoded++; -} - -#endif // defined(WITH_PROFILING) +/* + * Copyright (C) 2019 + * Matthias P. Braendli (matthias.braendli@mpb.li) + * + * This file is part of the welle.io. + * Many of the ideas as implemented in welle.io are derived from + * other work, made available through the GNU general Public License. + * All copyrights of the original authors are recognized. + * + * welle.io is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * welle.io 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with welle.io; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#if defined(WITH_PROFILING) +#include +#include +#include +#include +#include + +#include "various/profiling.h" + +using namespace std; + +static Profiler profiler; + +Profiler& get_profiler() { + return profiler; +} + +#define MARK_TO_CSTR_CASE(m) case ProfilingMark::m: return #m; +const char* mark_to_cstr(const ProfilingMark& m) { + switch (m) { + MARK_TO_CSTR_CASE(NotSynced) + MARK_TO_CSTR_CASE(SyncOnEndNull) + MARK_TO_CSTR_CASE(SyncOnPhase) + MARK_TO_CSTR_CASE(FindIndex) + MARK_TO_CSTR_CASE(DataSymbols) + MARK_TO_CSTR_CASE(PushAllSymbols) + MARK_TO_CSTR_CASE(OnNewNull) + MARK_TO_CSTR_CASE(DecodeTII) + + MARK_TO_CSTR_CASE(ProcessPRS) + MARK_TO_CSTR_CASE(ProcessSymbol) + MARK_TO_CSTR_CASE(Deinterleaver) + MARK_TO_CSTR_CASE(FICHandler) + MARK_TO_CSTR_CASE(MSCHandler) + MARK_TO_CSTR_CASE(SymbolProcessed) + + MARK_TO_CSTR_CASE(DAGetMSCData) + MARK_TO_CSTR_CASE(DADeinterleave) + MARK_TO_CSTR_CASE(DADeconvolve) + MARK_TO_CSTR_CASE(DADispersal) + MARK_TO_CSTR_CASE(DADecode) + MARK_TO_CSTR_CASE(DADone) + } + + return "unknown"; +} + +Profiler::Profiler() { + clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &startup_time_cputime); + clock_gettime(CLOCK_MONOTONIC, &startup_time_monotonic); +} + +struct timespec& operator+=(struct timespec& t1, const struct timespec& t2) { + t1.tv_sec += t2.tv_sec; + t1.tv_nsec += t2.tv_nsec; + + if (t1.tv_nsec >= 1000000000ll) { + t1.tv_nsec -= 1000000000ll; + t1.tv_sec += 1; + } + + return t1; +} + +struct timespec operator-(struct timespec t1, struct timespec t2) { + struct timespec t; + + if (t1.tv_sec < t2.tv_sec) { + cerr << "******************* Timestamp difference negative" << endl; + t.tv_sec = 0; + t.tv_nsec = 0; + return t; + } + + t.tv_sec = t1.tv_sec - t2.tv_sec; + + if (t1.tv_nsec >= t2.tv_nsec) { + t.tv_nsec = t1.tv_nsec - t2.tv_nsec; + } + else { + t.tv_sec -= 1; + t.tv_nsec = 1000000000ll + t1.tv_nsec - t2.tv_nsec; + } + return t; +} + +std::ostream& operator<<(std::ostream& out, const struct timespec& ts) +{ + char nanos[32]; + snprintf(nanos, 31, "%09ld", ts.tv_nsec); + return out << ts.tv_sec << "." << nanos; +} + +Profiler::~Profiler() { + struct timespec stop_time_cputime; + struct timespec stop_time_monotonic; + clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &stop_time_cputime); + clock_gettime(CLOCK_MONOTONIC, &stop_time_monotonic); + + ofstream dump("profiling_points.csv"); + dump << "thread_id,mark,time_sec,time_ns" << endl; + for (const auto tp: m_timepoints) { + for (const auto points : tp.second) { + dump << tp.first << "," << + mark_to_cstr(points.p) << "," << + points.timestamp.tv_sec << "," << + points.timestamp.tv_nsec << endl; + } + } + + ofstream profiling("profiling_stats.csv"); + profiling << "cputime,start," << startup_time_cputime << endl; + profiling << "cputime,stop," << stop_time_cputime << endl; + profiling << "monotonic,start," << startup_time_monotonic << endl; + profiling << "monotonic,stop," << stop_time_monotonic << endl; + profiling << "cputime,diff," << stop_time_cputime - startup_time_cputime << endl; + profiling << "monotonic,diff," << stop_time_monotonic - startup_time_monotonic << endl; + profiling << "frames,decoded," << num_frames_decoded << endl; + + // See http://www.graphviz.org/documentation/ + ofstream graph("profiling.dot"); + + graph << "digraph G { " << endl; + + size_t count = 0; + + for (const auto tp: m_timepoints) { + if (tp.second.size() < 2) { + continue; + } + + graph << "subgraph cluster_" << tp.first << " { " << endl; + graph << "colorscheme=\"gnbu8\";" << endl; + graph << "bgcolor=" << (count % 8) + 1 << ";" << endl; + count++; + + map, struct timespec> from_to_times; + + for (auto it = tp.second.begin(); next(it) != tp.second.end(); ++it) { + from_to_times[make_pair(it->p, next(it)->p)] += next(it)->timestamp - it->timestamp; + } + + double maxw = 0; + for (auto& d : from_to_times) { + double w = log10(1 + d.second.tv_sec * 1000 + d.second.tv_nsec / 1000000); + if (w > maxw) maxw = w; + } + + for (auto& d : from_to_times) { + int w = (d.second.tv_sec * 1000 + d.second.tv_nsec / 1000000); + + char color[16]; + snprintf(color, 15, "#%02x%02x%02x", (int)(255 * log10(w+1)/maxw), 0, 0); + + graph << mark_to_cstr(d.first.first) << " -> " << mark_to_cstr(d.first.second) << + " [color=\"" << color << "\"" + " label=\"" << w << "ms\"" + "];" << endl; + } + graph << "}" << endl; + } + graph << "}" << endl; +} + +void Profiler::save_time(const ProfilingMark m) { + const auto id = this_thread::get_id(); + + struct timespec now; + clock_gettime(CLOCK_THREAD_CPUTIME_ID, &now); + + lock_guard lock(m_mutex); + m_timepoints[id].emplace_back(now, m); +} + +void Profiler::frame_decoded() { + num_frames_decoded++; +} + +#endif // defined(WITH_PROFILING) diff --git a/src/dabdsp/profiling.h b/src/dabdsp/profiling.h index 173fe4d..63c95cc 100644 --- a/src/dabdsp/profiling.h +++ b/src/dabdsp/profiling.h @@ -1,102 +1,102 @@ -/* - * Copyright (C) 2019 - * Matthias P. Braendli (matthias.braendli@mpb.li) - * - * This file is part of the welle.io. - * Many of the ideas as implemented in welle.io are derived from - * other work, made available through the GNU general Public License. - * All copyrights of the original authors are recognized. - * - * welle.io is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * welle.io 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with welle.io; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - - -#if defined(WITH_PROFILING) - -#include -#include -#include -#include -#include -#include - -#define PROFILE(m) get_profiler().save_time(ProfilingMark::m) -#define PROFILE_FRAME_DECODED() get_profiler().frame_decoded() - -enum class ProfilingMark { - NotSynced, - SyncOnEndNull, - SyncOnPhase, - FindIndex, - DataSymbols, - PushAllSymbols, - OnNewNull, - DecodeTII, - - ProcessPRS, - ProcessSymbol, - Deinterleaver, - FICHandler, - MSCHandler, - SymbolProcessed, - - DAGetMSCData, - DADeinterleave, - DADeconvolve, - DADispersal, - DADecode, - DADone, -}; - -struct ProfilingTimepoint { - ProfilingTimepoint( - struct timespec timestamp, - ProfilingMark p) : - timestamp(timestamp), - p(p) { } - - struct timespec timestamp; - ProfilingMark p; -}; - -class Profiler -{ - public: - Profiler(); - Profiler(const Profiler&) = delete; - Profiler& operator=(const Profiler&) = delete; - ~Profiler(); - - void save_time(const ProfilingMark m); - void frame_decoded(); - private: - std::mutex m_mutex; - std::unordered_map< - std::thread::id, - std::list > m_timepoints; - - struct timespec startup_time_cputime; - struct timespec startup_time_monotonic; - size_t num_frames_decoded = 0; -}; - -Profiler& get_profiler(void); - -#else -# define PROFILE(m) -# define PROFILE_FRAME_DECODED() -#endif // defined(WITH_PROFILING) - +/* + * Copyright (C) 2019 + * Matthias P. Braendli (matthias.braendli@mpb.li) + * + * This file is part of the welle.io. + * Many of the ideas as implemented in welle.io are derived from + * other work, made available through the GNU general Public License. + * All copyrights of the original authors are recognized. + * + * welle.io is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * welle.io 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with welle.io; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + + +#if defined(WITH_PROFILING) + +#include +#include +#include +#include +#include +#include + +#define PROFILE(m) get_profiler().save_time(ProfilingMark::m) +#define PROFILE_FRAME_DECODED() get_profiler().frame_decoded() + +enum class ProfilingMark { + NotSynced, + SyncOnEndNull, + SyncOnPhase, + FindIndex, + DataSymbols, + PushAllSymbols, + OnNewNull, + DecodeTII, + + ProcessPRS, + ProcessSymbol, + Deinterleaver, + FICHandler, + MSCHandler, + SymbolProcessed, + + DAGetMSCData, + DADeinterleave, + DADeconvolve, + DADispersal, + DADecode, + DADone, +}; + +struct ProfilingTimepoint { + ProfilingTimepoint( + struct timespec timestamp, + ProfilingMark p) : + timestamp(timestamp), + p(p) { } + + struct timespec timestamp; + ProfilingMark p; +}; + +class Profiler +{ + public: + Profiler(); + Profiler(const Profiler&) = delete; + Profiler& operator=(const Profiler&) = delete; + ~Profiler(); + + void save_time(const ProfilingMark m); + void frame_decoded(); + private: + std::mutex m_mutex; + std::unordered_map< + std::thread::id, + std::list > m_timepoints; + + struct timespec startup_time_cputime; + struct timespec startup_time_monotonic; + size_t num_frames_decoded = 0; +}; + +Profiler& get_profiler(void); + +#else +# define PROFILE(m) +# define PROFILE_FRAME_DECODED() +#endif // defined(WITH_PROFILING) + diff --git a/src/dabdsp/protTables.cpp b/src/dabdsp/protTables.cpp index 7737ce5..0f69048 100644 --- a/src/dabdsp/protTables.cpp +++ b/src/dabdsp/protTables.cpp @@ -1,57 +1,57 @@ -/* - * - * Copyright (C) 2013 - * Jan van Katwijk (J.vanKatwijk@gmail.com) - * Lazy Chair Programming - * - * This file is part of the SDR-J (JSDR). - * SDR-J is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * SDR-J 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with SDR-J; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ -#include "protTables.h" - -static const -int8_t p_codes[24][32] = { - { 1,1,0,0, 1,0,0,0, 1,0,0,0, 1,0,0,0, 1,0,0,0, 1,0,0,0, 1,0,0,0, 1,0,0,0},// 1 - { 1,1,0,0, 1,0,0,0, 1,0,0,0, 1,0,0,0, 1,1,0,0, 1,0,0,0, 1,0,0,0, 1,0,0,0},// 2 - { 1,1,0,0, 1,0,0,0, 1,1,0,0, 1,0,0,0, 1,1,0,0, 1,0,0,0, 1,0,0,0, 1,0,0,0},// 3 - { 1,1,0,0, 1,0,0,0, 1,1,0,0, 1,0,0,0, 1,1,0,0, 1,0,0,0, 1,1,0,0, 1,0,0,0},// 4 - { 1,1,0,0, 1,1,0,0, 1,1,0,0, 1,0,0,0, 1,1,0,0, 1,0,0,0, 1,1,0,0, 1,0,0,0},// 5 - { 1,1,0,0, 1,1,0,0, 1,1,0,0, 1,0,0,0, 1,1,0,0, 1,1,0,0, 1,1,0,0, 1,0,0,0},// 6 - { 1,1,0,0, 1,1,0,0, 1,1,0,0, 1,1,0,0, 1,1,0,0, 1,1,0,0, 1,1,0,0, 1,0,0,0},// 7 - { 1,1,0,0, 1,1,0,0, 1,1,0,0, 1,1,0,0, 1,1,0,0, 1,1,0,0, 1,1,0,0, 1,1,0,0},// 8 - { 1,1,1,0, 1,1,0,0, 1,1,0,0, 1,1,0,0, 1,1,0,0, 1,1,0,0, 1,1,0,0, 1,1,0,0},// 9 - { 1,1,1,0, 1,1,0,0, 1,1,0,0, 1,1,0,0, 1,1,1,0, 1,1,0,0, 1,1,0,0, 1,1,0,0},// 10 - { 1,1,1,0, 1,1,0,0, 1,1,1,0, 1,1,0,0, 1,1,1,0, 1,1,0,0, 1,1,0,0, 1,1,0,0},// 11 - { 1,1,1,0, 1,1,0,0, 1,1,1,0, 1,1,0,0, 1,1,1,0, 1,1,0,0, 1,1,1,0, 1,1,0,0},// 12 - { 1,1,1,0, 1,1,1,0, 1,1,1,0, 1,1,0,0, 1,1,1,0, 1,1,0,0, 1,1,1,0, 1,1,0,0},// 13 - { 1,1,1,0, 1,1,1,0, 1,1,1,0, 1,1,0,0, 1,1,1,0, 1,1,1,0, 1,1,1,0, 1,1,0,0},// 14 - { 1,1,1,0, 1,1,1,0, 1,1,1,0, 1,1,1,0, 1,1,1,0, 1,1,1,0, 1,1,1,0, 1,1,0,0},// 15 - { 1,1,1,0, 1,1,1,0, 1,1,1,0, 1,1,1,0, 1,1,1,0, 1,1,1,0, 1,1,1,0, 1,1,1,0},// 16 - { 1,1,1,1, 1,1,1,0, 1,1,1,0, 1,1,1,0, 1,1,1,0, 1,1,1,0, 1,1,1,0, 1,1,1,0},// 17 - { 1,1,1,1, 1,1,1,0, 1,1,1,0, 1,1,1,0, 1,1,1,1, 1,1,1,0, 1,1,1,0, 1,1,1,0},// 18 - { 1,1,1,1, 1,1,1,0, 1,1,1,1, 1,1,1,0, 1,1,1,1, 1,1,1,0, 1,1,1,0, 1,1,1,0},// 19 - { 1,1,1,1, 1,1,1,0, 1,1,1,1, 1,1,1,0, 1,1,1,1, 1,1,1,0, 1,1,1,1, 1,1,1,0},// 20 - { 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,0, 1,1,1,1, 1,1,1,0, 1,1,1,1, 1,1,1,0},// 21 - { 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,0, 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,0},// 22 - { 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,0},// 23 - { 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1} // 24 -}; - -const int8_t *getPCodes(int16_t x) -{ - return p_codes[x]; -} - +/* + * + * Copyright (C) 2013 + * Jan van Katwijk (J.vanKatwijk@gmail.com) + * Lazy Chair Programming + * + * This file is part of the SDR-J (JSDR). + * SDR-J is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * SDR-J 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with SDR-J; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include "protTables.h" + +static const +int8_t p_codes[24][32] = { + { 1,1,0,0, 1,0,0,0, 1,0,0,0, 1,0,0,0, 1,0,0,0, 1,0,0,0, 1,0,0,0, 1,0,0,0},// 1 + { 1,1,0,0, 1,0,0,0, 1,0,0,0, 1,0,0,0, 1,1,0,0, 1,0,0,0, 1,0,0,0, 1,0,0,0},// 2 + { 1,1,0,0, 1,0,0,0, 1,1,0,0, 1,0,0,0, 1,1,0,0, 1,0,0,0, 1,0,0,0, 1,0,0,0},// 3 + { 1,1,0,0, 1,0,0,0, 1,1,0,0, 1,0,0,0, 1,1,0,0, 1,0,0,0, 1,1,0,0, 1,0,0,0},// 4 + { 1,1,0,0, 1,1,0,0, 1,1,0,0, 1,0,0,0, 1,1,0,0, 1,0,0,0, 1,1,0,0, 1,0,0,0},// 5 + { 1,1,0,0, 1,1,0,0, 1,1,0,0, 1,0,0,0, 1,1,0,0, 1,1,0,0, 1,1,0,0, 1,0,0,0},// 6 + { 1,1,0,0, 1,1,0,0, 1,1,0,0, 1,1,0,0, 1,1,0,0, 1,1,0,0, 1,1,0,0, 1,0,0,0},// 7 + { 1,1,0,0, 1,1,0,0, 1,1,0,0, 1,1,0,0, 1,1,0,0, 1,1,0,0, 1,1,0,0, 1,1,0,0},// 8 + { 1,1,1,0, 1,1,0,0, 1,1,0,0, 1,1,0,0, 1,1,0,0, 1,1,0,0, 1,1,0,0, 1,1,0,0},// 9 + { 1,1,1,0, 1,1,0,0, 1,1,0,0, 1,1,0,0, 1,1,1,0, 1,1,0,0, 1,1,0,0, 1,1,0,0},// 10 + { 1,1,1,0, 1,1,0,0, 1,1,1,0, 1,1,0,0, 1,1,1,0, 1,1,0,0, 1,1,0,0, 1,1,0,0},// 11 + { 1,1,1,0, 1,1,0,0, 1,1,1,0, 1,1,0,0, 1,1,1,0, 1,1,0,0, 1,1,1,0, 1,1,0,0},// 12 + { 1,1,1,0, 1,1,1,0, 1,1,1,0, 1,1,0,0, 1,1,1,0, 1,1,0,0, 1,1,1,0, 1,1,0,0},// 13 + { 1,1,1,0, 1,1,1,0, 1,1,1,0, 1,1,0,0, 1,1,1,0, 1,1,1,0, 1,1,1,0, 1,1,0,0},// 14 + { 1,1,1,0, 1,1,1,0, 1,1,1,0, 1,1,1,0, 1,1,1,0, 1,1,1,0, 1,1,1,0, 1,1,0,0},// 15 + { 1,1,1,0, 1,1,1,0, 1,1,1,0, 1,1,1,0, 1,1,1,0, 1,1,1,0, 1,1,1,0, 1,1,1,0},// 16 + { 1,1,1,1, 1,1,1,0, 1,1,1,0, 1,1,1,0, 1,1,1,0, 1,1,1,0, 1,1,1,0, 1,1,1,0},// 17 + { 1,1,1,1, 1,1,1,0, 1,1,1,0, 1,1,1,0, 1,1,1,1, 1,1,1,0, 1,1,1,0, 1,1,1,0},// 18 + { 1,1,1,1, 1,1,1,0, 1,1,1,1, 1,1,1,0, 1,1,1,1, 1,1,1,0, 1,1,1,0, 1,1,1,0},// 19 + { 1,1,1,1, 1,1,1,0, 1,1,1,1, 1,1,1,0, 1,1,1,1, 1,1,1,0, 1,1,1,1, 1,1,1,0},// 20 + { 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,0, 1,1,1,1, 1,1,1,0, 1,1,1,1, 1,1,1,0},// 21 + { 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,0, 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,0},// 22 + { 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,0},// 23 + { 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1} // 24 +}; + +const int8_t *getPCodes(int16_t x) +{ + return p_codes[x]; +} + diff --git a/src/dabdsp/protTables.h b/src/dabdsp/protTables.h index 8e75023..0064fe3 100644 --- a/src/dabdsp/protTables.h +++ b/src/dabdsp/protTables.h @@ -1,31 +1,31 @@ -/* - * - * Copyright (C) 2013 - * Jan van Katwijk (J.vanKatwijk@gmail.com) - * Lazy Chair Programming - * - * This file is part of the SDR-J (JSDR). - * SDR-J is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * SDR-J 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with SDR-J; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - -#ifndef PROTTABLES -#define PROTTABLES -#include - -const int8_t *getPCodes(int16_t); - -#endif - +/* + * + * Copyright (C) 2013 + * Jan van Katwijk (J.vanKatwijk@gmail.com) + * Lazy Chair Programming + * + * This file is part of the SDR-J (JSDR). + * SDR-J is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * SDR-J 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with SDR-J; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef PROTTABLES +#define PROTTABLES +#include + +const int8_t *getPCodes(int16_t); + +#endif + diff --git a/src/dabdsp/protection.h b/src/dabdsp/protection.h index dd0b497..7843826 100644 --- a/src/dabdsp/protection.h +++ b/src/dabdsp/protection.h @@ -1,39 +1,39 @@ -/* - * Copyright (C) 2017 - * Jan van Katwijk (J.vanKatwijk@gmail.com) - * Lazy Chair Programming - * - * This file is part of the SDR-J (JSDR). - * SDR-J is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * SDR-J 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with SDR-J; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - * - * Simple base class for combining uep and eep deconvolvers - */ -#ifndef __PROTECTION -#define __PROTECTION - -#include -#include "dab-constants.h" - -extern uint8_t PI_X[]; - -class Protection -{ - public: - virtual ~Protection() = default; - virtual bool deconvolve(const softbit_t *, int32_t, uint8_t *) = 0; -}; -#endif - +/* + * Copyright (C) 2017 + * Jan van Katwijk (J.vanKatwijk@gmail.com) + * Lazy Chair Programming + * + * This file is part of the SDR-J (JSDR). + * SDR-J is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * SDR-J 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with SDR-J; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * Simple base class for combining uep and eep deconvolvers + */ +#ifndef __PROTECTION +#define __PROTECTION + +#include +#include "dab-constants.h" + +extern uint8_t PI_X[]; + +class Protection +{ + public: + virtual ~Protection() = default; + virtual bool deconvolve(const softbit_t *, int32_t, uint8_t *) = 0; +}; +#endif + diff --git a/src/dabdsp/radio-controller.h b/src/dabdsp/radio-controller.h index f500ab1..fdd4faa 100644 --- a/src/dabdsp/radio-controller.h +++ b/src/dabdsp/radio-controller.h @@ -1,228 +1,228 @@ -/* - * Copyright (C) 2018 - * Matthias P. Braendli (matthias.braendli@mpb.li) - * - * Copyright (C) 2017 - * Albrecht Lohofener (albrechtloh@gmx.de) - * - * This file is based on SDR-J - * Copyright (C) 2010, 2011, 2012 - * Jan van Katwijk (J.vanKatwijk@gmail.com) - * - * This file is part of the welle.io. - * Many of the ideas as implemented in welle.io are derived from - * other work, made available through the GNU general Public License. - * All copyrights of the original authors are recognized. - * - * welle.io is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * welle.io 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with welle.io; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - -#ifndef RADIOCONTROLLER_H -#define RADIOCONTROLLER_H - -#include -#include -#include -#include -#include "dab-constants.h" - -struct dab_date_time_t { - int year = 0; - int month = 0; - int day = 0; - int hour = 0; - int minutes = 0; - int seconds = 0; - - // Information decoded from local time offset in FIG 0/9 - int hourOffset = 0; - int minuteOffset = 0; -}; - - -struct tii_measurement_t { - int comb = 0; - int pattern = 0; - float error = 0; - int delay_samples = 0; - - float getDelayKm(void) const; -}; - -struct mot_file_t { - std::vector data; - int content_sub_type; - - std::string content_name; - std::string click_through_url; - uint8_t category; - uint8_t slide_id; - std::string category_title; -}; - -enum class message_level_t { Information, Error }; - -/* Definition of the interface all radio controllers must implement. - * The RadioController handles events that are common to all programmes - * being listened to. - * All functions starting with "on" are callbacks for the backend. - */ -class RadioControllerInterface { - public: - /* Signal-to-Noise Ratio was calculated. snr is a value in dB. */ - virtual void onSNR(float snr) {} - - /* The frequency corrector estimated a new correction. The frequency - * correction consists of a coarse and a fine value, both having the - * same units, measured in number of samples. */ - virtual void onFrequencyCorrectorChange(int fine, int coarse) {} - - /* Indicate if receive signal synchronisation was acquired or lost. */ - virtual void onSyncChange(bool isSync) {} - - /* Indicate if a signal is suspected on the currently tuned frequency. - * This is useful to accelerate the scan. */ - virtual void onSignalPresence(bool isSignal) {} - - /* A new service with service ID sId was detected. */ - virtual void onServiceDetected(uint32_t sId) {} - - // MB: Added (fib-processor.cpp) - /* When a service label changes */ - virtual void onSetServiceLabel(uint32_t sId, DabLabel& label) {} - - /* When the ensemble changes */ - virtual void onNewEnsemble(uint16_t eId) {} - - /* When the ensemble label changes */ - virtual void onSetEnsembleLabel(DabLabel& label) {} - - virtual void onDateTimeUpdate(const dab_date_time_t& dateTime) {} - - /* For every FIB, tell if the CRC check passed. fib points to a bit-vector with 256 bits of FIB data */ - virtual void onFIBDecodeSuccess(bool crcCheckOk, const uint8_t* fib) {} - - /* When a new channel impulse response vector was calculated */ - virtual void onNewImpulseResponse(std::vector&& data) {} - - /* When new constellation points are available. data contains - * (L-1) * K / OfdmDecoder::constellationDecimation points. */ - virtual void onConstellationPoints(std::vector&& data) {} - - /* When a new null symbol vector was received. - * Data contains the samples of the complete NULL symbol. */ - virtual void onNewNullSymbol(std::vector&& data) {} - - /* When TII information for a comb/pattern pair is available */ - virtual void onTIIMeasurement(tii_measurement_t&& m) {} - - // MB: Removed, unused by welle.io backend - ///* When a information or warning message should be printed */ - //virtual void onMessage(message_level_t level, const std::string& text, const std::string& text2 = std::string()) = 0; - - /* The receiver has shutdown due to a failure in the input device */ - virtual void onInputFailure(void) {} -}; - -/* A Programme Handler is associated to each tuned programme in the ensemble. - */ -class ProgrammeHandlerInterface { - public: - /* Count the number of frame errors from the MP2, AAC or data - * decoder. */ - virtual void onFrameErrors(int frameErrors) {} - - /* New audio data is available. The sampleRate and the - * stereo indicator may change at any time. - * mode is an information related to the audio encoding - * used. */ - virtual void onNewAudio(std::vector&& audioData, int sampleRate, const std::string& mode) {} - - /* (DAB+ only) Reed-Solomon decoding error indicator, and - * number of corrected errors. - * The function will also be called in the absence of errors, - * with an count of 0. */ - virtual void onRsErrors(bool uncorrectedErrors, int numCorrectedErrors) {} - - /* (DAB+ only) Audio Decoder error */ - virtual void onAacErrors(int aacErrors) {} - - /* A new Dynamic Label was decoded. - * label is utf-8 encoded. */ - virtual void onNewDynamicLabel(const std::string& label) {} - - /* A slide was decoded. data contains the raw bytes, and subtype - * defines the data format: - * 0x01 for JPEG, 0x03 for PNG */ - virtual void onMOT(const mot_file_t& mot_file) {} - - /* Called when the PAD decoder notices a mismatch between announced - * and effective X-PAD length. - */ - virtual void onPADLengthError(size_t announced_xpad_len, size_t xpad_len) {} -}; - -enum class DeviceParam { - BiasTee, - SoapySDRAntenna, - SoapySDRDriverArgs, - SoapySDRClockSource, -}; - -// Original InputInterface -// -///* Definition of the interface all input devices must implement */ -//class InputInterface { -//public: -// virtual ~InputInterface() {} -// virtual void setFrequency(int frequency) = 0; -// virtual int getFrequency(void) const = 0; -// virtual bool is_ok(void) = 0; -// virtual bool restart(void) = 0; -// virtual void stop(void) = 0; -// virtual void reset(void) = 0; -// virtual int32_t getSamples(DSPCOMPLEX* buffer, int32_t size) = 0; -// virtual std::vector getSpectrumSamples(int size) = 0; -// virtual int32_t getSamplesToRead(void) = 0; -// virtual float setGain(int gain) = 0; -// virtual float getGain(void) const = 0; -// virtual int getGainCount(void) = 0; -// virtual void setAgc(bool agc) = 0; -// virtual std::string getDescription(void) = 0; -// -// virtual bool setDeviceParam(DeviceParam param, int value) { -// (void)param; (void)value; -// return false; -// } -// -// virtual bool setDeviceParam(DeviceParam param, const std::string& value) { -// (void)param; (void)value; -// return false; -// } -//}; - -// Pared down InputInterface -// -class InputInterface { -public: - virtual ~InputInterface() {} - virtual bool is_ok(void) = 0; - virtual bool restart(void) = 0; - virtual int32_t getSamples(DSPCOMPLEX* buffer, int32_t size) = 0; - virtual int32_t getSamplesToRead(void) = 0; -}; - -#endif +/* + * Copyright (C) 2018 + * Matthias P. Braendli (matthias.braendli@mpb.li) + * + * Copyright (C) 2017 + * Albrecht Lohofener (albrechtloh@gmx.de) + * + * This file is based on SDR-J + * Copyright (C) 2010, 2011, 2012 + * Jan van Katwijk (J.vanKatwijk@gmail.com) + * + * This file is part of the welle.io. + * Many of the ideas as implemented in welle.io are derived from + * other work, made available through the GNU general Public License. + * All copyrights of the original authors are recognized. + * + * welle.io is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * welle.io 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with welle.io; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef RADIOCONTROLLER_H +#define RADIOCONTROLLER_H + +#include +#include +#include +#include +#include "dab-constants.h" + +struct dab_date_time_t { + int year = 0; + int month = 0; + int day = 0; + int hour = 0; + int minutes = 0; + int seconds = 0; + + // Information decoded from local time offset in FIG 0/9 + int hourOffset = 0; + int minuteOffset = 0; +}; + + +struct tii_measurement_t { + int comb = 0; + int pattern = 0; + float error = 0; + int delay_samples = 0; + + float getDelayKm(void) const; +}; + +struct mot_file_t { + std::vector data; + int content_sub_type; + + std::string content_name; + std::string click_through_url; + uint8_t category; + uint8_t slide_id; + std::string category_title; +}; + +enum class message_level_t { Information, Error }; + +/* Definition of the interface all radio controllers must implement. + * The RadioController handles events that are common to all programmes + * being listened to. + * All functions starting with "on" are callbacks for the backend. + */ +class RadioControllerInterface { + public: + /* Signal-to-Noise Ratio was calculated. snr is a value in dB. */ + virtual void onSNR(float snr) {} + + /* The frequency corrector estimated a new correction. The frequency + * correction consists of a coarse and a fine value, both having the + * same units, measured in number of samples. */ + virtual void onFrequencyCorrectorChange(int fine, int coarse) {} + + /* Indicate if receive signal synchronisation was acquired or lost. */ + virtual void onSyncChange(bool isSync) {} + + /* Indicate if a signal is suspected on the currently tuned frequency. + * This is useful to accelerate the scan. */ + virtual void onSignalPresence(bool isSignal) {} + + /* A new service with service ID sId was detected. */ + virtual void onServiceDetected(uint32_t sId) {} + + // MB: Added (fib-processor.cpp) + /* When a service label changes */ + virtual void onSetServiceLabel(uint32_t sId, DabLabel& label) {} + + /* When the ensemble changes */ + virtual void onNewEnsemble(uint16_t eId) {} + + /* When the ensemble label changes */ + virtual void onSetEnsembleLabel(DabLabel& label) {} + + virtual void onDateTimeUpdate(const dab_date_time_t& dateTime) {} + + /* For every FIB, tell if the CRC check passed. fib points to a bit-vector with 256 bits of FIB data */ + virtual void onFIBDecodeSuccess(bool crcCheckOk, const uint8_t* fib) {} + + /* When a new channel impulse response vector was calculated */ + virtual void onNewImpulseResponse(std::vector&& data) {} + + /* When new constellation points are available. data contains + * (L-1) * K / OfdmDecoder::constellationDecimation points. */ + virtual void onConstellationPoints(std::vector&& data) {} + + /* When a new null symbol vector was received. + * Data contains the samples of the complete NULL symbol. */ + virtual void onNewNullSymbol(std::vector&& data) {} + + /* When TII information for a comb/pattern pair is available */ + virtual void onTIIMeasurement(tii_measurement_t&& m) {} + + // MB: Removed, unused by welle.io backend + ///* When a information or warning message should be printed */ + //virtual void onMessage(message_level_t level, const std::string& text, const std::string& text2 = std::string()) = 0; + + /* The receiver has shutdown due to a failure in the input device */ + virtual void onInputFailure(void) {} +}; + +/* A Programme Handler is associated to each tuned programme in the ensemble. + */ +class ProgrammeHandlerInterface { + public: + /* Count the number of frame errors from the MP2, AAC or data + * decoder. */ + virtual void onFrameErrors(int frameErrors) {} + + /* New audio data is available. The sampleRate and the + * stereo indicator may change at any time. + * mode is an information related to the audio encoding + * used. */ + virtual void onNewAudio(std::vector&& audioData, int sampleRate, const std::string& mode) {} + + /* (DAB+ only) Reed-Solomon decoding error indicator, and + * number of corrected errors. + * The function will also be called in the absence of errors, + * with an count of 0. */ + virtual void onRsErrors(bool uncorrectedErrors, int numCorrectedErrors) {} + + /* (DAB+ only) Audio Decoder error */ + virtual void onAacErrors(int aacErrors) {} + + /* A new Dynamic Label was decoded. + * label is utf-8 encoded. */ + virtual void onNewDynamicLabel(const std::string& label) {} + + /* A slide was decoded. data contains the raw bytes, and subtype + * defines the data format: + * 0x01 for JPEG, 0x03 for PNG */ + virtual void onMOT(const mot_file_t& mot_file) {} + + /* Called when the PAD decoder notices a mismatch between announced + * and effective X-PAD length. + */ + virtual void onPADLengthError(size_t announced_xpad_len, size_t xpad_len) {} +}; + +enum class DeviceParam { + BiasTee, + SoapySDRAntenna, + SoapySDRDriverArgs, + SoapySDRClockSource, +}; + +// Original InputInterface +// +///* Definition of the interface all input devices must implement */ +//class InputInterface { +//public: +// virtual ~InputInterface() {} +// virtual void setFrequency(int frequency) = 0; +// virtual int getFrequency(void) const = 0; +// virtual bool is_ok(void) = 0; +// virtual bool restart(void) = 0; +// virtual void stop(void) = 0; +// virtual void reset(void) = 0; +// virtual int32_t getSamples(DSPCOMPLEX* buffer, int32_t size) = 0; +// virtual std::vector getSpectrumSamples(int size) = 0; +// virtual int32_t getSamplesToRead(void) = 0; +// virtual float setGain(int gain) = 0; +// virtual float getGain(void) const = 0; +// virtual int getGainCount(void) = 0; +// virtual void setAgc(bool agc) = 0; +// virtual std::string getDescription(void) = 0; +// +// virtual bool setDeviceParam(DeviceParam param, int value) { +// (void)param; (void)value; +// return false; +// } +// +// virtual bool setDeviceParam(DeviceParam param, const std::string& value) { +// (void)param; (void)value; +// return false; +// } +//}; + +// Pared down InputInterface +// +class InputInterface { +public: + virtual ~InputInterface() {} + virtual bool is_ok(void) = 0; + virtual bool restart(void) = 0; + virtual int32_t getSamples(DSPCOMPLEX* buffer, int32_t size) = 0; + virtual int32_t getSamplesToRead(void) = 0; +}; + +#endif diff --git a/src/dabdsp/radio-receiver-options.h b/src/dabdsp/radio-receiver-options.h index 347d8d6..7912f4d 100644 --- a/src/dabdsp/radio-receiver-options.h +++ b/src/dabdsp/radio-receiver-options.h @@ -1,85 +1,85 @@ -/* - * Copyright (C) 2019 - * Matthias P. Braendli (matthias.braendli@mpb.li) - * - * Copyright (C) 2017 - * Albrecht Lohofener (albrechtloh@gmx.de) - * - * This file is based on SDR-J - * Copyright (C) 2010, 2011, 2012 - * Jan van Katwijk (J.vanKatwijk@gmail.com) - * - * This file is part of the welle.io. - * Many of the ideas as implemented in welle.io are derived from - * other work, made available through the GNU general Public License. - * All copyrights of the original authors are recognized. - * - * welle.io is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * welle.io 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with welle.io; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - -#pragma once - -// see OFDMProcessor::processPRS() for more information about these methods -enum class FreqsyncMethod { GetMiddle = 0, CorrelatePRS = 1, PatternOfZeros = 2 }; - -enum class FFTPlacementMethod { - /* Old method: places the FFT on the strongest peak, which must be at least - * 3 times as high as the average. - * - * Issues: can lock on a peak that is not the earlisest peak (multipath) - */ - StrongestPeak, - - /* Calculate peaks over bins of 25 samples, keep the 4 bins with the - * highest peaks, take the index from the peak in the earliest bin, but not - * any earlier than 500 samples. - * - * Issues: sometimes loses lock even in good receive conditions. - */ - EarliestPeakWithBinning, - - /* Apply a windowing function that selects the peak correlation, then - * place the FFT where the correlation goes above a threshold earliest. - * - * Issues: performance not yet assessed. - */ - ThresholdBeforePeak, -}; - -// Default uses the old algorithm until the issues of the new one are solved. -constexpr auto DEFAULT_FFT_PLACEMENT = FFTPlacementMethod::ThresholdBeforePeak; - -// Configuration for the backend -struct RadioReceiverOptions { - // Select the algorithm used in the OFDMProcessor PRS sync logic - // to place the FFT window for demodulation. - // - // Issues: initial lock can take longer than with original algorithm. - FFTPlacementMethod fftPlacementMethod = DEFAULT_FFT_PLACEMENT; - - // Set to true to enable the TII decoder. Default is false because it is - // consumes CPU resources. - bool decodeTII = false; - - // Good receivers with accurate clocks do not need the coarse corrector. - // Disabling it can accelerate lock. - bool disableCoarseCorrector = false; - - // Which method to use for the freqsyncmethod used in the coarse corrector. - // Has no effect when coarse corrector is disabled. - FreqsyncMethod freqsyncMethod = FreqsyncMethod::PatternOfZeros; -}; - +/* + * Copyright (C) 2019 + * Matthias P. Braendli (matthias.braendli@mpb.li) + * + * Copyright (C) 2017 + * Albrecht Lohofener (albrechtloh@gmx.de) + * + * This file is based on SDR-J + * Copyright (C) 2010, 2011, 2012 + * Jan van Katwijk (J.vanKatwijk@gmail.com) + * + * This file is part of the welle.io. + * Many of the ideas as implemented in welle.io are derived from + * other work, made available through the GNU general Public License. + * All copyrights of the original authors are recognized. + * + * welle.io is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * welle.io 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with welle.io; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#pragma once + +// see OFDMProcessor::processPRS() for more information about these methods +enum class FreqsyncMethod { GetMiddle = 0, CorrelatePRS = 1, PatternOfZeros = 2 }; + +enum class FFTPlacementMethod { + /* Old method: places the FFT on the strongest peak, which must be at least + * 3 times as high as the average. + * + * Issues: can lock on a peak that is not the earlisest peak (multipath) + */ + StrongestPeak, + + /* Calculate peaks over bins of 25 samples, keep the 4 bins with the + * highest peaks, take the index from the peak in the earliest bin, but not + * any earlier than 500 samples. + * + * Issues: sometimes loses lock even in good receive conditions. + */ + EarliestPeakWithBinning, + + /* Apply a windowing function that selects the peak correlation, then + * place the FFT where the correlation goes above a threshold earliest. + * + * Issues: performance not yet assessed. + */ + ThresholdBeforePeak, +}; + +// Default uses the old algorithm until the issues of the new one are solved. +constexpr auto DEFAULT_FFT_PLACEMENT = FFTPlacementMethod::ThresholdBeforePeak; + +// Configuration for the backend +struct RadioReceiverOptions { + // Select the algorithm used in the OFDMProcessor PRS sync logic + // to place the FFT window for demodulation. + // + // Issues: initial lock can take longer than with original algorithm. + FFTPlacementMethod fftPlacementMethod = DEFAULT_FFT_PLACEMENT; + + // Set to true to enable the TII decoder. Default is false because it is + // consumes CPU resources. + bool decodeTII = false; + + // Good receivers with accurate clocks do not need the coarse corrector. + // Disabling it can accelerate lock. + bool disableCoarseCorrector = false; + + // Which method to use for the freqsyncmethod used in the coarse corrector. + // Has no effect when coarse corrector is disabled. + FreqsyncMethod freqsyncMethod = FreqsyncMethod::PatternOfZeros; +}; + diff --git a/src/dabdsp/radio-receiver.cpp b/src/dabdsp/radio-receiver.cpp index df4775d..ae2deaf 100644 --- a/src/dabdsp/radio-receiver.cpp +++ b/src/dabdsp/radio-receiver.cpp @@ -1,231 +1,231 @@ -/* - * Copyright (C) 2018 - * Matthias P. Braendli (matthias.braendli@mpb.li) - * - * Copyright (C) 2017 - * Albrecht Lohofener (albrechtloh@gmx.de) - * - * This file is based on SDR-J - * Copyright (C) 2010, 2011, 2012 - * Jan van Katwijk (J.vanKatwijk@gmail.com) - * - * This file is part of the welle.io. - * Many of the ideas as implemented in welle.io are derived from - * other work, made available through the GNU general Public License. - * All copyrights of the original authors are recognized. - * - * welle.io is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * welle.io 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with welle.io; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - -#include -#include -#include -#include -#include "radio-receiver.h" - -using namespace std; - -const char* fftPlacementMethodToString(FFTPlacementMethod fft_placement) -{ - switch (fft_placement) { - case FFTPlacementMethod::EarliestPeakWithBinning: - return "EarliestPeakWithBinning"; - case FFTPlacementMethod::StrongestPeak: - return "StrongestPeak"; - case FFTPlacementMethod::ThresholdBeforePeak: - return "ThresholdBeforePeak"; - } - throw std::logic_error("Unhandled fft placement"); -} - -const char* freqSyncMethodToString(FreqsyncMethod method) -{ - switch (method) { - case FreqsyncMethod::CorrelatePRS: - return "CorrelatePRS"; - case FreqsyncMethod::GetMiddle: - return "GetMiddle"; - case FreqsyncMethod::PatternOfZeros: - return "PatternOfZeros"; - } - throw std::logic_error("Unhandled freqsyncMethod placement"); -} - -RadioReceiver::RadioReceiver( - RadioControllerInterface& rci, - InputInterface& input, - RadioReceiverOptions rro, - int transmission_mode) : - params(transmission_mode), - mscHandler(params, false), - ficHandler(rci), - ofdmProcessor(input, - params, - rci, - mscHandler, - ficHandler, - rro) -{ } - -void RadioReceiver::restart(bool doScan) -{ - ofdmProcessor.set_scanMode(doScan); - mscHandler.stopProcessing(); - ficHandler.clearEnsemble(); - ofdmProcessor.restart(); -} - -void RadioReceiver::restart_decoder() -{ - mscHandler.stopProcessing(); - ficHandler.clearEnsemble(); -} - -void RadioReceiver::stop() -{ - ofdmProcessor.stop(); - mscHandler.stopProcessing(); - ficHandler.clearEnsemble(); -} - -void RadioReceiver::setReceiverOptions(const RadioReceiverOptions rro) -{ - string fsm; - switch (rro.freqsyncMethod) { - case FreqsyncMethod::GetMiddle: fsm = "GetMiddle"; break; - case FreqsyncMethod::CorrelatePRS: fsm = "CorrelatePRS"; break; - case FreqsyncMethod::PatternOfZeros: fsm = "PatternOfZeros"; break; - } - - clog << "New Receiver Options: " << - "TII: " << rro.decodeTII << - " disable coarse corr: " << rro.disableCoarseCorrector << - " freqsync: " << fsm << - " fft placement: " << fftPlacementMethodToString(rro.fftPlacementMethod) << endl; - ofdmProcessor.setReceiverOptions(rro); -} - -bool RadioReceiver::playSingleProgramme(ProgrammeHandlerInterface& handler, - const std::string& dumpFileName, const Service& s) -{ - return playProgramme(handler, s, dumpFileName, true); -} - -bool RadioReceiver::addServiceToDecode(ProgrammeHandlerInterface& handler, - const std::string& dumpFileName, const Service& s) -{ - return playProgramme(handler, s, dumpFileName, false); -} - -bool RadioReceiver::removeServiceToDecode(const Service& s) -{ - const auto comps = ficHandler.fibProcessor.getComponents(s); - for (const auto& sc : comps) { - if (sc.transportMode() == TransportMode::Audio) { - const auto& subch = ficHandler.fibProcessor.getSubchannel(sc); - if (subch.valid()) { - return mscHandler.removeSubchannel(subch); - } - } - } - return false; -} - -bool RadioReceiver::playProgramme(ProgrammeHandlerInterface& handler, - const Service& s, const std::string& dumpFileName, bool unique) -{ - const auto comps = ficHandler.fibProcessor.getComponents(s); - for (const auto& sc : comps) { - if (sc.transportMode() == TransportMode::Audio) { - const auto& subch = ficHandler.fibProcessor.getSubchannel(sc); - - if (subch.valid()) { - if (unique) { - mscHandler.stopProcessing(); - } - - if (sc.audioType() == AudioServiceComponentType::DAB || - sc.audioType() == AudioServiceComponentType::DABPlus) { - mscHandler.addSubchannel( - handler, sc.audioType(), dumpFileName, subch); - return true; - } - } - } - } - - return false; -} - -uint16_t RadioReceiver::getEnsembleId(void) const -{ - return ficHandler.fibProcessor.getEnsembleId(); -} - -uint8_t RadioReceiver::getEnsembleEcc(void) const -{ - return ficHandler.fibProcessor.getEnsembleEcc(); -} - -DabLabel RadioReceiver::getEnsembleLabel(void) const -{ - return ficHandler.fibProcessor.getEnsembleLabel(); -} - -std::vector RadioReceiver::getServiceList(void) const -{ - return ficHandler.fibProcessor.getServiceList(); -} - -Service RadioReceiver::getService(uint32_t sId) const -{ - return ficHandler.fibProcessor.getService(sId); -} - -std::list RadioReceiver::getComponents(const Service& s) const -{ - return ficHandler.fibProcessor.getComponents(s); -} - -bool RadioReceiver::serviceHasAudioComponent(const Service& s) const -{ - for (const auto& sc : getComponents(s)) { - if (sc.transportMode() == TransportMode::Audio and - (sc.audioType() == AudioServiceComponentType::DAB or - sc.audioType() == AudioServiceComponentType::DABPlus)) { - return true; - } - } - - return false; -} - -Subchannel RadioReceiver::getSubchannel(const ServiceComponent& sc) const -{ - return ficHandler.fibProcessor.getSubchannel(sc); -} - -DABParams& RadioReceiver::getParams() -{ - return params; -} - -RadioReceiverStats RadioReceiver::getReceiverStats() const -{ - RadioReceiverStats s; - s.timeLastFCT0Frame = ficHandler.fibProcessor.getTimeLastFCT0Frame(); - return s; -} +/* + * Copyright (C) 2018 + * Matthias P. Braendli (matthias.braendli@mpb.li) + * + * Copyright (C) 2017 + * Albrecht Lohofener (albrechtloh@gmx.de) + * + * This file is based on SDR-J + * Copyright (C) 2010, 2011, 2012 + * Jan van Katwijk (J.vanKatwijk@gmail.com) + * + * This file is part of the welle.io. + * Many of the ideas as implemented in welle.io are derived from + * other work, made available through the GNU general Public License. + * All copyrights of the original authors are recognized. + * + * welle.io is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * welle.io 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with welle.io; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include "radio-receiver.h" + +using namespace std; + +const char* fftPlacementMethodToString(FFTPlacementMethod fft_placement) +{ + switch (fft_placement) { + case FFTPlacementMethod::EarliestPeakWithBinning: + return "EarliestPeakWithBinning"; + case FFTPlacementMethod::StrongestPeak: + return "StrongestPeak"; + case FFTPlacementMethod::ThresholdBeforePeak: + return "ThresholdBeforePeak"; + } + throw std::logic_error("Unhandled fft placement"); +} + +const char* freqSyncMethodToString(FreqsyncMethod method) +{ + switch (method) { + case FreqsyncMethod::CorrelatePRS: + return "CorrelatePRS"; + case FreqsyncMethod::GetMiddle: + return "GetMiddle"; + case FreqsyncMethod::PatternOfZeros: + return "PatternOfZeros"; + } + throw std::logic_error("Unhandled freqsyncMethod placement"); +} + +RadioReceiver::RadioReceiver( + RadioControllerInterface& rci, + InputInterface& input, + RadioReceiverOptions rro, + int transmission_mode) : + params(transmission_mode), + mscHandler(params, false), + ficHandler(rci), + ofdmProcessor(input, + params, + rci, + mscHandler, + ficHandler, + rro) +{ } + +void RadioReceiver::restart(bool doScan) +{ + ofdmProcessor.set_scanMode(doScan); + mscHandler.stopProcessing(); + ficHandler.clearEnsemble(); + ofdmProcessor.restart(); +} + +void RadioReceiver::restart_decoder() +{ + mscHandler.stopProcessing(); + ficHandler.clearEnsemble(); +} + +void RadioReceiver::stop() +{ + ofdmProcessor.stop(); + mscHandler.stopProcessing(); + ficHandler.clearEnsemble(); +} + +void RadioReceiver::setReceiverOptions(const RadioReceiverOptions rro) +{ + string fsm; + switch (rro.freqsyncMethod) { + case FreqsyncMethod::GetMiddle: fsm = "GetMiddle"; break; + case FreqsyncMethod::CorrelatePRS: fsm = "CorrelatePRS"; break; + case FreqsyncMethod::PatternOfZeros: fsm = "PatternOfZeros"; break; + } + + clog << "New Receiver Options: " << + "TII: " << rro.decodeTII << + " disable coarse corr: " << rro.disableCoarseCorrector << + " freqsync: " << fsm << + " fft placement: " << fftPlacementMethodToString(rro.fftPlacementMethod) << endl; + ofdmProcessor.setReceiverOptions(rro); +} + +bool RadioReceiver::playSingleProgramme(ProgrammeHandlerInterface& handler, + const std::string& dumpFileName, const Service& s) +{ + return playProgramme(handler, s, dumpFileName, true); +} + +bool RadioReceiver::addServiceToDecode(ProgrammeHandlerInterface& handler, + const std::string& dumpFileName, const Service& s) +{ + return playProgramme(handler, s, dumpFileName, false); +} + +bool RadioReceiver::removeServiceToDecode(const Service& s) +{ + const auto comps = ficHandler.fibProcessor.getComponents(s); + for (const auto& sc : comps) { + if (sc.transportMode() == TransportMode::Audio) { + const auto& subch = ficHandler.fibProcessor.getSubchannel(sc); + if (subch.valid()) { + return mscHandler.removeSubchannel(subch); + } + } + } + return false; +} + +bool RadioReceiver::playProgramme(ProgrammeHandlerInterface& handler, + const Service& s, const std::string& dumpFileName, bool unique) +{ + const auto comps = ficHandler.fibProcessor.getComponents(s); + for (const auto& sc : comps) { + if (sc.transportMode() == TransportMode::Audio) { + const auto& subch = ficHandler.fibProcessor.getSubchannel(sc); + + if (subch.valid()) { + if (unique) { + mscHandler.stopProcessing(); + } + + if (sc.audioType() == AudioServiceComponentType::DAB || + sc.audioType() == AudioServiceComponentType::DABPlus) { + mscHandler.addSubchannel( + handler, sc.audioType(), dumpFileName, subch); + return true; + } + } + } + } + + return false; +} + +uint16_t RadioReceiver::getEnsembleId(void) const +{ + return ficHandler.fibProcessor.getEnsembleId(); +} + +uint8_t RadioReceiver::getEnsembleEcc(void) const +{ + return ficHandler.fibProcessor.getEnsembleEcc(); +} + +DabLabel RadioReceiver::getEnsembleLabel(void) const +{ + return ficHandler.fibProcessor.getEnsembleLabel(); +} + +std::vector RadioReceiver::getServiceList(void) const +{ + return ficHandler.fibProcessor.getServiceList(); +} + +Service RadioReceiver::getService(uint32_t sId) const +{ + return ficHandler.fibProcessor.getService(sId); +} + +std::list RadioReceiver::getComponents(const Service& s) const +{ + return ficHandler.fibProcessor.getComponents(s); +} + +bool RadioReceiver::serviceHasAudioComponent(const Service& s) const +{ + for (const auto& sc : getComponents(s)) { + if (sc.transportMode() == TransportMode::Audio and + (sc.audioType() == AudioServiceComponentType::DAB or + sc.audioType() == AudioServiceComponentType::DABPlus)) { + return true; + } + } + + return false; +} + +Subchannel RadioReceiver::getSubchannel(const ServiceComponent& sc) const +{ + return ficHandler.fibProcessor.getSubchannel(sc); +} + +DABParams& RadioReceiver::getParams() +{ + return params; +} + +RadioReceiverStats RadioReceiver::getReceiverStats() const +{ + RadioReceiverStats s; + s.timeLastFCT0Frame = ficHandler.fibProcessor.getTimeLastFCT0Frame(); + return s; +} diff --git a/src/dabdsp/radio-receiver.h b/src/dabdsp/radio-receiver.h index 95de077..2e0c6e9 100644 --- a/src/dabdsp/radio-receiver.h +++ b/src/dabdsp/radio-receiver.h @@ -1,118 +1,118 @@ -/* - * Copyright (C) 2020 - * Matthias P. Braendli (matthias.braendli@mpb.li) - * - * Copyright (C) 2017 - * Albrecht Lohofener (albrechtloh@gmx.de) - * - * This file is based on SDR-J - * Copyright (C) 2010, 2011, 2012 - * Jan van Katwijk (J.vanKatwijk@gmail.com) - * - * This file is part of the welle.io. - * Many of the ideas as implemented in welle.io are derived from - * other work, made available through the GNU general Public License. - * All copyrights of the original authors are recognized. - * - * welle.io is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * welle.io 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with welle.io; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - -#ifndef RADIO_RECEIVER_H -#define RADIO_RECEIVER_H - -#include -#include -#include -#include "radio-controller.h" -#include "radio-receiver-options.h" -#include "fic-handler.h" -#include "msc-handler.h" -#include "ofdm-processor.h" - -const char* fftPlacementMethodToString(FFTPlacementMethod fft_placement); -const char* freqSyncMethodToString(FreqsyncMethod method); - -struct RadioReceiverStats { - std::chrono::system_clock::time_point timeLastFCT0Frame; -}; - -class RadioReceiver { - public: - RadioReceiver( - RadioControllerInterface& rci, - InputInterface& input, - RadioReceiverOptions rro, - int transmission_mode = 1); - - /* Restart the receiver, and specify if we want - * to scan or receive. */ - void restart(bool doScan); - - /* Keep the demodulator running, but clear the data - * decoders (both FIC and MSC) */ - void restart_decoder(); - - void stop(); - - /* Update the currently running receiver with new configuration */ - void setReceiverOptions(const RadioReceiverOptions rro); - - /* Play the audio component of the service. Returns true if an - * audio subchannel was found and tuned to. */ - bool playSingleProgramme(ProgrammeHandlerInterface& handler, - const std::string& dumpFileName, const Service& s); - - bool addServiceToDecode(ProgrammeHandlerInterface& handler, - const std::string& dumpFileName, const Service& s); - - bool removeServiceToDecode(const Service& s); - - uint16_t getEnsembleId(void) const; - uint8_t getEnsembleEcc(void) const; - DabLabel getEnsembleLabel(void) const; - std::vector getServiceList(void) const; - - /* Returns a service with sid 0 in case it is missing */ - // TODO use std::optional once using C++17 makes sense - Service getService(uint32_t sId) const; - - std::list getComponents(const Service& s) const; - bool serviceHasAudioComponent(const Service& s) const; - - /* Return the subchannel corresponding to the given component. - * This can fail, in which case the Subchannel returned has - * subch field equals to -1 - */ - Subchannel getSubchannel(const ServiceComponent& sc) const; - - DABParams& getParams(); - - RadioReceiverStats getReceiverStats() const; - - private: - bool playProgramme(ProgrammeHandlerInterface& handler, - const Service& s, - const std::string& dumpFileName, - bool unique); - - DABParams params; // Defaults to TM1 parameters - - MscHandler mscHandler; - FicHandler ficHandler; - OFDMProcessor ofdmProcessor; -}; - -#endif +/* + * Copyright (C) 2020 + * Matthias P. Braendli (matthias.braendli@mpb.li) + * + * Copyright (C) 2017 + * Albrecht Lohofener (albrechtloh@gmx.de) + * + * This file is based on SDR-J + * Copyright (C) 2010, 2011, 2012 + * Jan van Katwijk (J.vanKatwijk@gmail.com) + * + * This file is part of the welle.io. + * Many of the ideas as implemented in welle.io are derived from + * other work, made available through the GNU general Public License. + * All copyrights of the original authors are recognized. + * + * welle.io is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * welle.io 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with welle.io; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef RADIO_RECEIVER_H +#define RADIO_RECEIVER_H + +#include +#include +#include +#include "radio-controller.h" +#include "radio-receiver-options.h" +#include "fic-handler.h" +#include "msc-handler.h" +#include "ofdm-processor.h" + +const char* fftPlacementMethodToString(FFTPlacementMethod fft_placement); +const char* freqSyncMethodToString(FreqsyncMethod method); + +struct RadioReceiverStats { + std::chrono::system_clock::time_point timeLastFCT0Frame; +}; + +class RadioReceiver { + public: + RadioReceiver( + RadioControllerInterface& rci, + InputInterface& input, + RadioReceiverOptions rro, + int transmission_mode = 1); + + /* Restart the receiver, and specify if we want + * to scan or receive. */ + void restart(bool doScan); + + /* Keep the demodulator running, but clear the data + * decoders (both FIC and MSC) */ + void restart_decoder(); + + void stop(); + + /* Update the currently running receiver with new configuration */ + void setReceiverOptions(const RadioReceiverOptions rro); + + /* Play the audio component of the service. Returns true if an + * audio subchannel was found and tuned to. */ + bool playSingleProgramme(ProgrammeHandlerInterface& handler, + const std::string& dumpFileName, const Service& s); + + bool addServiceToDecode(ProgrammeHandlerInterface& handler, + const std::string& dumpFileName, const Service& s); + + bool removeServiceToDecode(const Service& s); + + uint16_t getEnsembleId(void) const; + uint8_t getEnsembleEcc(void) const; + DabLabel getEnsembleLabel(void) const; + std::vector getServiceList(void) const; + + /* Returns a service with sid 0 in case it is missing */ + // TODO use std::optional once using C++17 makes sense + Service getService(uint32_t sId) const; + + std::list getComponents(const Service& s) const; + bool serviceHasAudioComponent(const Service& s) const; + + /* Return the subchannel corresponding to the given component. + * This can fail, in which case the Subchannel returned has + * subch field equals to -1 + */ + Subchannel getSubchannel(const ServiceComponent& sc) const; + + DABParams& getParams(); + + RadioReceiverStats getReceiverStats() const; + + private: + bool playProgramme(ProgrammeHandlerInterface& handler, + const Service& s, + const std::string& dumpFileName, + bool unique); + + DABParams params; // Defaults to TM1 parameters + + MscHandler mscHandler; + FicHandler ficHandler; + OFDMProcessor ofdmProcessor; +}; + +#endif diff --git a/src/dabdsp/ringbuffer.h b/src/dabdsp/ringbuffer.h index 26ae05e..0847e7c 100644 --- a/src/dabdsp/ringbuffer.h +++ b/src/dabdsp/ringbuffer.h @@ -1,344 +1,344 @@ -/* - * $Id: pa_ringbuffer.c 1738 2011-08-18 11:47:28Z rossb $ - * Portable Audio I/O Library - * Ring Buffer utility. - * - * Author: Phil Burk, http://www.softsynth.com - * modified for SMP safety on Mac OS X by Bjorn Roche - * modified for SMP safety on Linux by Leland Lucius - * also, allowed for const where possible - * modified for multiple-byte-sized data elements by Sven Fischer - * - * Note that this is safe only for a single-thread reader and a - * single-thread writer. - * - * This program uses the PortAudio Portable Audio Library. - * For more information see: http://www.portaudio.com - * Copyright (c) 1999-2000 Ross Bencina and Phil Burk - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files - * (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, - * publish, distribute, sublicense, and/or sell copies of the Software, - * and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR - * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF - * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/* - * Copyright (C) 2018 - * Albrecht Lohofener (albrechtloh@gmx.de) - * - * Copyright (C) 2008, 2009, 2010 - * Jan van Katwijk (J.vanKatwijk@gmail.com) - * Lazy Chair Computing - * - * The ringbuffer here is a rewrite of the ringbuffer used in the PA code - * All rights remain with their owners - * This file is part of the welle.io. - * Many of the ideas as implemented in welle.io are derived from - * other work, made available through the GNU general Public License. - * All copyrights of the original authors are recognized. - * - * welle.io is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * welle.io 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ESDR; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#ifndef RING_BUFFER_H -#define RING_BUFFER_H - -#include -#include -#include -#include -#include -#include - -/* - * a simple ringbuffer, lockfree, however only for a - * single reader and a single writer. - * Mostly used for getting samples from or to the soundcard - */ -#if defined(__APPLE__) -# include - /* Here are the memory barrier functions. Mac OS X only provides - full memory barriers, so the three types of barriers are the same, - however, these barriers are superior to compiler-based ones. */ -# define PaUtil_FullMemoryBarrier() OSMemoryBarrier() -# define PaUtil_ReadMemoryBarrier() OSMemoryBarrier() -# define PaUtil_WriteMemoryBarrier() OSMemoryBarrier() -#elif defined(__GNUC__) - /* GCC >= 4.1 has built-in intrinsics. We'll use those */ -# if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1) -# define PaUtil_FullMemoryBarrier() __sync_synchronize() -# define PaUtil_ReadMemoryBarrier() __sync_synchronize() -# define PaUtil_WriteMemoryBarrier() __sync_synchronize() - /* as a fallback, GCC understands volatile asm and "memory" to mean it - * should not reorder memory read/writes */ -# elif defined( __PPC__ ) -# define PaUtil_FullMemoryBarrier() asm volatile("sync":::"memory") -# define PaUtil_ReadMemoryBarrier() asm volatile("sync":::"memory") -# define PaUtil_WriteMemoryBarrier() asm volatile("sync":::"memory") -# elif defined( __i386__ ) || defined( __i486__ ) || defined( __i586__ ) || defined( __i686__ ) || defined( __x86_64__ ) -# define PaUtil_FullMemoryBarrier() asm volatile("mfence":::"memory") -# define PaUtil_ReadMemoryBarrier() asm volatile("lfence":::"memory") -# define PaUtil_WriteMemoryBarrier() asm volatile("sfence":::"memory") -# else -# ifdef ALLOW_SMP_DANGERS -# warning Memory barriers not defined on this system or system unknown -# warning For SMP safety, you should fix this. -# define PaUtil_FullMemoryBarrier() -# define PaUtil_ReadMemoryBarrier() -# define PaUtil_WriteMemoryBarrier() -# else -# error Memory barriers are not defined on this system. You can still compile by defining ALLOW_SMP_DANGERS, but SMP safety will not be guaranteed. -# endif -# endif -#elif defined(_WINDOWS) -# include -# define PaUtil_FullMemoryBarrier() std::atomic_thread_fence(std::memory_order_seq_cst); -# define PaUtil_ReadMemoryBarrier() std::atomic_thread_fence(std::memory_order_seq_cst); -# define PaUtil_WriteMemoryBarrier() std::atomic_thread_fence(std::memory_order_seq_cst); -#else -# ifdef ALLOW_SMP_DANGERS -# // warning Memory barriers not defined on this system or system unknown -# // warning For SMP safety, you should fix this. -# define PaUtil_FullMemoryBarrier() -# define PaUtil_ReadMemoryBarrier() -# define PaUtil_WriteMemoryBarrier() -# else -# error Memory barriers are not defined on this system. You can still compile by defining ALLOW_SMP_DANGERS, but SMP safety will not be guaranteed. -# endif -#endif - -// Base implementation -template -class RingBuffer -{ - private: - uint32_t bufferSize; - volatile uint32_t writeIndex; - volatile uint32_t readIndex; - uint32_t bigMask; - uint32_t smallMask; - std::vector buffer; - - protected: - void onDroppedData(int32_t droppedElements) { - (void) droppedElements; - // In case a warning should be output, do it here - } - - public: - RingBuffer(uint32_t elementCount) { - if (((elementCount - 1) & elementCount) != 0) - elementCount = 2 * 16384; /* default */ - - bufferSize = elementCount; - buffer.resize(2 * bufferSize * sizeof (elementtype)); - writeIndex = 0; - readIndex = 0; - smallMask = (elementCount)- 1; - bigMask = (elementCount * 2) - 1; - } - - /* - * functions for checking available data for reading and space - * for writing - */ - int32_t GetBufferSize(void) { - return bufferSize; - } - - int32_t GetRingBufferReadAvailable (void) { - return (writeIndex - readIndex) & bigMask; - } - - int32_t ReadSpace (void){ - return GetRingBufferReadAvailable (); - } - - int32_t GetRingBufferWriteAvailable (void) { - return bufferSize - GetRingBufferReadAvailable (); - } - - int32_t WriteSpace (void) { - return GetRingBufferWriteAvailable (); - } - - void FlushRingBuffer () { - writeIndex = 0; - readIndex = 0; - } - /* ensure that previous writes are seen before we update the write index - (write after write) - */ - int32_t AdvanceRingBufferWriteIndex (int32_t elementCount) { - PaUtil_WriteMemoryBarrier(); - return writeIndex = (writeIndex + elementCount) & bigMask; - } - - /* ensure that previous reads (copies out of the ring buffer) are - * always completed before updating (writing) the read index. - * (write-after-read) => full barrier - */ - int32_t AdvanceRingBufferReadIndex (int32_t elementCount) { - PaUtil_FullMemoryBarrier(); - return readIndex = (readIndex + elementCount) & bigMask; - } - - /*************************************************************************** - ** Get address of region(s) to which we can write data. - ** If the region is contiguous, size2 will be zero. - ** If non-contiguous, size2 will be the size of second region. - ** Returns room available to be written or elementCount, whichever is smaller. - */ - int32_t GetRingBufferWriteRegions (uint32_t elementCount, - void **dataPtr1, int32_t *sizePtr1, - void **dataPtr2, int32_t *sizePtr2 ) { - uint32_t index; - uint32_t available = GetRingBufferWriteAvailable (); - - if (elementCount > available) - elementCount = available; - - /* Check to see if write is not contiguous. */ - index = writeIndex & smallMask; - if ((index + elementCount) > bufferSize ) { - /* Write data in two blocks that wrap the buffer. */ - int32_t firstHalf = bufferSize - index; - *dataPtr1 = &buffer[index * sizeof(elementtype)]; - *sizePtr1 = firstHalf; - *dataPtr2 = &buffer[0]; - *sizePtr2 = elementCount - firstHalf; - } - else { // fits - *dataPtr1 = &buffer[index * sizeof(elementtype)]; - *sizePtr1 = elementCount; - *dataPtr2 = NULL; - *sizePtr2 = 0; - } - - if (available > 0) - PaUtil_FullMemoryBarrier(); /* (write-after-read) => full barrier */ - - return elementCount; - } - - /*************************************************************************** - ** Get address of region(s) from which we can read data. - ** If the region is contiguous, size2 will be zero. - ** If non-contiguous, size2 will be the size of second region. - ** Returns room available to be read or elementCount, whichever is smaller. - */ - int32_t GetRingBufferReadRegions (uint32_t elementCount, - void **dataPtr1, int32_t *sizePtr1, - void **dataPtr2, int32_t *sizePtr2) { - uint32_t index; - uint32_t available = GetRingBufferReadAvailable (); /* doesn't use memory barrier */ - - if (elementCount > available) - elementCount = available; - - /* Check to see if read is not contiguous. */ - index = readIndex & smallMask; - if ((index + elementCount) > bufferSize) { - /* Write data in two blocks that wrap the buffer. */ - int32_t firstHalf = bufferSize - index; - *dataPtr1 = &buffer[index * sizeof(elementtype)]; - *sizePtr1 = firstHalf; - *dataPtr2 = &buffer[0]; - *sizePtr2 = elementCount - firstHalf; - } - else { - *dataPtr1 = &buffer[index * sizeof(elementtype)]; - *sizePtr1 = elementCount; - *dataPtr2 = NULL; - *sizePtr2 = 0; - } - - if (available) - PaUtil_ReadMemoryBarrier(); /* (read-after-read) => read barrier */ - - return elementCount; - } - - int32_t putDataIntoBuffer (const void *data, int32_t elementCount) { - int32_t size1, size2, numWritten; - void *data1; - void *data2; - - int32_t freeSpace = GetRingBufferWriteAvailable(); - int32_t droppedElements = elementCount - freeSpace; - if(droppedElements > 0) - onDroppedData(droppedElements); - - numWritten = GetRingBufferWriteRegions (elementCount, - &data1, &size1, - &data2, &size2 ); - if (size2 > 0) { - memcpy (data1, data, size1 * sizeof(elementtype)); - data = ((char *)data) + size1 * sizeof(elementtype); - memcpy (data2, data, size2 * sizeof(elementtype)); - } - else - memcpy (data1, data, size1 * sizeof(elementtype)); - - AdvanceRingBufferWriteIndex (numWritten ); - return numWritten; - } - - int32_t getDataFromBuffer (void *data, int32_t elementCount ) { - int32_t size1, size2, numRead; - void *data1; - void *data2; - - numRead = GetRingBufferReadRegions (elementCount, - &data1, &size1, - &data2, &size2 ); - if (size2 > 0) { - memcpy (data, data1, size1 * sizeof(elementtype)); - data = ((char *)data) + size1 * sizeof(elementtype); - memcpy (data, data2, size2 * sizeof(elementtype)); - } - else - memcpy (data, data1, size1 * sizeof(elementtype)); - - AdvanceRingBufferReadIndex (numRead ); - return numRead; - } - - int32_t skipDataInBuffer (int32_t n_values) { - // ensure that we have the correct read and write indices - PaUtil_FullMemoryBarrier (); - if (n_values > GetRingBufferReadAvailable ()) - n_values = GetRingBufferReadAvailable (); - AdvanceRingBufferReadIndex (n_values); - return n_values; - } - -}; - -#endif // RING_BUFFER_H +/* + * $Id: pa_ringbuffer.c 1738 2011-08-18 11:47:28Z rossb $ + * Portable Audio I/O Library + * Ring Buffer utility. + * + * Author: Phil Burk, http://www.softsynth.com + * modified for SMP safety on Mac OS X by Bjorn Roche + * modified for SMP safety on Linux by Leland Lucius + * also, allowed for const where possible + * modified for multiple-byte-sized data elements by Sven Fischer + * + * Note that this is safe only for a single-thread reader and a + * single-thread writer. + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * Copyright (C) 2018 + * Albrecht Lohofener (albrechtloh@gmx.de) + * + * Copyright (C) 2008, 2009, 2010 + * Jan van Katwijk (J.vanKatwijk@gmail.com) + * Lazy Chair Computing + * + * The ringbuffer here is a rewrite of the ringbuffer used in the PA code + * All rights remain with their owners + * This file is part of the welle.io. + * Many of the ideas as implemented in welle.io are derived from + * other work, made available through the GNU general Public License. + * All copyrights of the original authors are recognized. + * + * welle.io is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * welle.io 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ESDR; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef RING_BUFFER_H +#define RING_BUFFER_H + +#include +#include +#include +#include +#include +#include + +/* + * a simple ringbuffer, lockfree, however only for a + * single reader and a single writer. + * Mostly used for getting samples from or to the soundcard + */ +#if defined(__APPLE__) +# include + /* Here are the memory barrier functions. Mac OS X only provides + full memory barriers, so the three types of barriers are the same, + however, these barriers are superior to compiler-based ones. */ +# define PaUtil_FullMemoryBarrier() OSMemoryBarrier() +# define PaUtil_ReadMemoryBarrier() OSMemoryBarrier() +# define PaUtil_WriteMemoryBarrier() OSMemoryBarrier() +#elif defined(__GNUC__) + /* GCC >= 4.1 has built-in intrinsics. We'll use those */ +# if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1) +# define PaUtil_FullMemoryBarrier() __sync_synchronize() +# define PaUtil_ReadMemoryBarrier() __sync_synchronize() +# define PaUtil_WriteMemoryBarrier() __sync_synchronize() + /* as a fallback, GCC understands volatile asm and "memory" to mean it + * should not reorder memory read/writes */ +# elif defined( __PPC__ ) +# define PaUtil_FullMemoryBarrier() asm volatile("sync":::"memory") +# define PaUtil_ReadMemoryBarrier() asm volatile("sync":::"memory") +# define PaUtil_WriteMemoryBarrier() asm volatile("sync":::"memory") +# elif defined( __i386__ ) || defined( __i486__ ) || defined( __i586__ ) || defined( __i686__ ) || defined( __x86_64__ ) +# define PaUtil_FullMemoryBarrier() asm volatile("mfence":::"memory") +# define PaUtil_ReadMemoryBarrier() asm volatile("lfence":::"memory") +# define PaUtil_WriteMemoryBarrier() asm volatile("sfence":::"memory") +# else +# ifdef ALLOW_SMP_DANGERS +# warning Memory barriers not defined on this system or system unknown +# warning For SMP safety, you should fix this. +# define PaUtil_FullMemoryBarrier() +# define PaUtil_ReadMemoryBarrier() +# define PaUtil_WriteMemoryBarrier() +# else +# error Memory barriers are not defined on this system. You can still compile by defining ALLOW_SMP_DANGERS, but SMP safety will not be guaranteed. +# endif +# endif +#elif defined(_WINDOWS) +# include +# define PaUtil_FullMemoryBarrier() std::atomic_thread_fence(std::memory_order_seq_cst); +# define PaUtil_ReadMemoryBarrier() std::atomic_thread_fence(std::memory_order_seq_cst); +# define PaUtil_WriteMemoryBarrier() std::atomic_thread_fence(std::memory_order_seq_cst); +#else +# ifdef ALLOW_SMP_DANGERS +# // warning Memory barriers not defined on this system or system unknown +# // warning For SMP safety, you should fix this. +# define PaUtil_FullMemoryBarrier() +# define PaUtil_ReadMemoryBarrier() +# define PaUtil_WriteMemoryBarrier() +# else +# error Memory barriers are not defined on this system. You can still compile by defining ALLOW_SMP_DANGERS, but SMP safety will not be guaranteed. +# endif +#endif + +// Base implementation +template +class RingBuffer +{ + private: + uint32_t bufferSize; + volatile uint32_t writeIndex; + volatile uint32_t readIndex; + uint32_t bigMask; + uint32_t smallMask; + std::vector buffer; + + protected: + void onDroppedData(int32_t droppedElements) { + (void) droppedElements; + // In case a warning should be output, do it here + } + + public: + RingBuffer(uint32_t elementCount) { + if (((elementCount - 1) & elementCount) != 0) + elementCount = 2 * 16384; /* default */ + + bufferSize = elementCount; + buffer.resize(2 * bufferSize * sizeof (elementtype)); + writeIndex = 0; + readIndex = 0; + smallMask = (elementCount)- 1; + bigMask = (elementCount * 2) - 1; + } + + /* + * functions for checking available data for reading and space + * for writing + */ + int32_t GetBufferSize(void) { + return bufferSize; + } + + int32_t GetRingBufferReadAvailable (void) { + return (writeIndex - readIndex) & bigMask; + } + + int32_t ReadSpace (void){ + return GetRingBufferReadAvailable (); + } + + int32_t GetRingBufferWriteAvailable (void) { + return bufferSize - GetRingBufferReadAvailable (); + } + + int32_t WriteSpace (void) { + return GetRingBufferWriteAvailable (); + } + + void FlushRingBuffer () { + writeIndex = 0; + readIndex = 0; + } + /* ensure that previous writes are seen before we update the write index + (write after write) + */ + int32_t AdvanceRingBufferWriteIndex (int32_t elementCount) { + PaUtil_WriteMemoryBarrier(); + return writeIndex = (writeIndex + elementCount) & bigMask; + } + + /* ensure that previous reads (copies out of the ring buffer) are + * always completed before updating (writing) the read index. + * (write-after-read) => full barrier + */ + int32_t AdvanceRingBufferReadIndex (int32_t elementCount) { + PaUtil_FullMemoryBarrier(); + return readIndex = (readIndex + elementCount) & bigMask; + } + + /*************************************************************************** + ** Get address of region(s) to which we can write data. + ** If the region is contiguous, size2 will be zero. + ** If non-contiguous, size2 will be the size of second region. + ** Returns room available to be written or elementCount, whichever is smaller. + */ + int32_t GetRingBufferWriteRegions (uint32_t elementCount, + void **dataPtr1, int32_t *sizePtr1, + void **dataPtr2, int32_t *sizePtr2 ) { + uint32_t index; + uint32_t available = GetRingBufferWriteAvailable (); + + if (elementCount > available) + elementCount = available; + + /* Check to see if write is not contiguous. */ + index = writeIndex & smallMask; + if ((index + elementCount) > bufferSize ) { + /* Write data in two blocks that wrap the buffer. */ + int32_t firstHalf = bufferSize - index; + *dataPtr1 = &buffer[index * sizeof(elementtype)]; + *sizePtr1 = firstHalf; + *dataPtr2 = &buffer[0]; + *sizePtr2 = elementCount - firstHalf; + } + else { // fits + *dataPtr1 = &buffer[index * sizeof(elementtype)]; + *sizePtr1 = elementCount; + *dataPtr2 = NULL; + *sizePtr2 = 0; + } + + if (available > 0) + PaUtil_FullMemoryBarrier(); /* (write-after-read) => full barrier */ + + return elementCount; + } + + /*************************************************************************** + ** Get address of region(s) from which we can read data. + ** If the region is contiguous, size2 will be zero. + ** If non-contiguous, size2 will be the size of second region. + ** Returns room available to be read or elementCount, whichever is smaller. + */ + int32_t GetRingBufferReadRegions (uint32_t elementCount, + void **dataPtr1, int32_t *sizePtr1, + void **dataPtr2, int32_t *sizePtr2) { + uint32_t index; + uint32_t available = GetRingBufferReadAvailable (); /* doesn't use memory barrier */ + + if (elementCount > available) + elementCount = available; + + /* Check to see if read is not contiguous. */ + index = readIndex & smallMask; + if ((index + elementCount) > bufferSize) { + /* Write data in two blocks that wrap the buffer. */ + int32_t firstHalf = bufferSize - index; + *dataPtr1 = &buffer[index * sizeof(elementtype)]; + *sizePtr1 = firstHalf; + *dataPtr2 = &buffer[0]; + *sizePtr2 = elementCount - firstHalf; + } + else { + *dataPtr1 = &buffer[index * sizeof(elementtype)]; + *sizePtr1 = elementCount; + *dataPtr2 = NULL; + *sizePtr2 = 0; + } + + if (available) + PaUtil_ReadMemoryBarrier(); /* (read-after-read) => read barrier */ + + return elementCount; + } + + int32_t putDataIntoBuffer (const void *data, int32_t elementCount) { + int32_t size1, size2, numWritten; + void *data1; + void *data2; + + int32_t freeSpace = GetRingBufferWriteAvailable(); + int32_t droppedElements = elementCount - freeSpace; + if(droppedElements > 0) + onDroppedData(droppedElements); + + numWritten = GetRingBufferWriteRegions (elementCount, + &data1, &size1, + &data2, &size2 ); + if (size2 > 0) { + memcpy (data1, data, size1 * sizeof(elementtype)); + data = ((char *)data) + size1 * sizeof(elementtype); + memcpy (data2, data, size2 * sizeof(elementtype)); + } + else + memcpy (data1, data, size1 * sizeof(elementtype)); + + AdvanceRingBufferWriteIndex (numWritten ); + return numWritten; + } + + int32_t getDataFromBuffer (void *data, int32_t elementCount ) { + int32_t size1, size2, numRead; + void *data1; + void *data2; + + numRead = GetRingBufferReadRegions (elementCount, + &data1, &size1, + &data2, &size2 ); + if (size2 > 0) { + memcpy (data, data1, size1 * sizeof(elementtype)); + data = ((char *)data) + size1 * sizeof(elementtype); + memcpy (data, data2, size2 * sizeof(elementtype)); + } + else + memcpy (data, data1, size1 * sizeof(elementtype)); + + AdvanceRingBufferReadIndex (numRead ); + return numRead; + } + + int32_t skipDataInBuffer (int32_t n_values) { + // ensure that we have the correct read and write indices + PaUtil_FullMemoryBarrier (); + if (n_values > GetRingBufferReadAvailable ()) + n_values = GetRingBufferReadAvailable (); + AdvanceRingBufferReadIndex (n_values); + return n_values; + } + +}; + +#endif // RING_BUFFER_H diff --git a/src/dabdsp/rs-common.h b/src/dabdsp/rs-common.h index e6b3dbe..e64eb39 100644 --- a/src/dabdsp/rs-common.h +++ b/src/dabdsp/rs-common.h @@ -1,26 +1,26 @@ -/* Stuff common to all the general-purpose Reed-Solomon codecs - * Copyright 2004 Phil Karn, KA9Q - * May be used under the terms of the GNU Lesser General Public License (LGPL) - */ - -/* Reed-Solomon codec control block */ -struct rs { - int mm; /* Bits per symbol */ - int nn; /* Symbols per block (= (1<= rs->nn) { - x -= rs->nn; - x = (x >> rs->mm) + (x & rs->nn); - } - return x; -} +/* Stuff common to all the general-purpose Reed-Solomon codecs + * Copyright 2004 Phil Karn, KA9Q + * May be used under the terms of the GNU Lesser General Public License (LGPL) + */ + +/* Reed-Solomon codec control block */ +struct rs { + int mm; /* Bits per symbol */ + int nn; /* Symbols per block (= (1<= rs->nn) { + x -= rs->nn; + x = (x >> rs->mm) + (x & rs->nn); + } + return x; +} diff --git a/src/dabdsp/tii-decoder.cpp b/src/dabdsp/tii-decoder.cpp index 2474f18..ddda742 100644 --- a/src/dabdsp/tii-decoder.cpp +++ b/src/dabdsp/tii-decoder.cpp @@ -1,390 +1,390 @@ -/* - * Copyright (C) 2018 - * Matthias P. Braendli (matthias.braendli@mpb.li) - * - * This file is part of the welle.io. - * - * welle.io is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * welle.io 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with welle.io; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ -#include -#include -#include -#include -#include -#include "tii-decoder.h" - -#ifdef _WINDOWS -#define _USE_MATH_DEFINES -#include -#endif - -using namespace std; - -static const int tii_pattern[70][8] = { // {{{ - {0,0,0,0,1,1,1,1}, - {0,0,0,1,0,1,1,1}, - {0,0,0,1,1,0,1,1}, - {0,0,0,1,1,1,0,1}, - {0,0,0,1,1,1,1,0}, - {0,0,1,0,0,1,1,1}, - {0,0,1,0,1,0,1,1}, - {0,0,1,0,1,1,0,1}, - {0,0,1,0,1,1,1,0}, - {0,0,1,1,0,0,1,1}, - {0,0,1,1,0,1,0,1}, - {0,0,1,1,0,1,1,0}, - {0,0,1,1,1,0,0,1}, - {0,0,1,1,1,0,1,0}, - {0,0,1,1,1,1,0,0}, - {0,1,0,0,0,1,1,1}, - {0,1,0,0,1,0,1,1}, - {0,1,0,0,1,1,0,1}, - {0,1,0,0,1,1,1,0}, - {0,1,0,1,0,0,1,1}, - {0,1,0,1,0,1,0,1}, - {0,1,0,1,0,1,1,0}, - {0,1,0,1,1,0,0,1}, - {0,1,0,1,1,0,1,0}, - {0,1,0,1,1,1,0,0}, - {0,1,1,0,0,0,1,1}, - {0,1,1,0,0,1,0,1}, - {0,1,1,0,0,1,1,0}, - {0,1,1,0,1,0,0,1}, - {0,1,1,0,1,0,1,0}, - {0,1,1,0,1,1,0,0}, - {0,1,1,1,0,0,0,1}, - {0,1,1,1,0,0,1,0}, - {0,1,1,1,0,1,0,0}, - {0,1,1,1,1,0,0,0}, - {1,0,0,0,0,1,1,1}, - {1,0,0,0,1,0,1,1}, - {1,0,0,0,1,1,0,1}, - {1,0,0,0,1,1,1,0}, - {1,0,0,1,0,0,1,1}, - {1,0,0,1,0,1,0,1}, - {1,0,0,1,0,1,1,0}, - {1,0,0,1,1,0,0,1}, - {1,0,0,1,1,0,1,0}, - {1,0,0,1,1,1,0,0}, - {1,0,1,0,0,0,1,1}, - {1,0,1,0,0,1,0,1}, - {1,0,1,0,0,1,1,0}, - {1,0,1,0,1,0,0,1}, - {1,0,1,0,1,0,1,0}, - {1,0,1,0,1,1,0,0}, - {1,0,1,1,0,0,0,1}, - {1,0,1,1,0,0,1,0}, - {1,0,1,1,0,1,0,0}, - {1,0,1,1,1,0,0,0}, - {1,1,0,0,0,0,1,1}, - {1,1,0,0,0,1,0,1}, - {1,1,0,0,0,1,1,0}, - {1,1,0,0,1,0,0,1}, - {1,1,0,0,1,0,1,0}, - {1,1,0,0,1,1,0,0}, - {1,1,0,1,0,0,0,1}, - {1,1,0,1,0,0,1,0}, - {1,1,0,1,0,1,0,0}, - {1,1,0,1,1,0,0,0}, - {1,1,1,0,0,0,0,1}, - {1,1,1,0,0,0,1,0}, - {1,1,1,0,0,1,0,0}, - {1,1,1,0,1,0,0,0}, - {1,1,1,1,0,0,0,0} }; // }}} - -bool operator==(const CombPattern& lhs, const CombPattern& rhs) -{ - return lhs.comb == rhs.comb and lhs.pattern == rhs.pattern; -} - -std::vector CombPattern::generateCarriers() const -{ - std::vector carriers; - carriers.reserve(32); - - for (carrier_t k = 0; k < 384; k++) { - for (int b = 0; b < 8; b++) { - if (k == 1 + 2*comb + 48*b and tii_pattern[pattern][b]) { - carriers.push_back(k - 769); - carriers.push_back(k - 769 + 1); - carriers.push_back(k - 385); - carriers.push_back(k - 385 + 1); - carriers.push_back(k); - carriers.push_back(k + 1); - carriers.push_back(k + 384); - carriers.push_back(k + 384 + 1); - } - } - } - - sort(carriers.begin(), carriers.end()); - - return carriers; -} - -float tii_measurement_t::getDelayKm(void) const -{ - constexpr float km_per_sample = 3e8f / 1000.0f / 2048000.0f; - return delay_samples * km_per_sample; -} - -TIIDecoder::TIIDecoder(const DABParams& params, RadioControllerInterface& ri) : - m_radioInterface(ri), - m_params(params), - m_fft_null(params.T_u), - m_fft_prs(params.T_u) -{ - if (m_params.dabMode != 1) { - clog << "TII decoder does not support mode " << m_params.dabMode << endl; - return; - } - - for (int c = 0; c < 24; c++) { - for (int p = 0; p < 70; p++) { - for (carrier_t k = 0; k < 384; k++) { - for (int b = 0; b < 8; b++) { - if (k == 1 + 2*c + 48*b and tii_pattern[p][b]) { - m_cp_per_carrier[k].emplace(c, p); - } - } - } - } - } - - m_thread = thread(&TIIDecoder::run, this); -} - -TIIDecoder::~TIIDecoder() -{ - unique_lock lock(m_state_mutex); - m_state = State::Abort; - lock.unlock(); - m_state_changed.notify_all(); - - if (m_thread.joinable()) { - m_thread.join(); - } -} - -void TIIDecoder::pushSymbols( - const std::vector& null, - const std::vector& prs) -{ - unique_lock lock(m_state_mutex); - if (m_state == State::Idle) { - m_prs = prs; - m_null = null; - m_state = State::NullPrsReady; - } - lock.unlock(); - m_state_changed.notify_all(); -} - -void TIIDecoder::run() -{ - const size_t spacing = m_params.T_u; - const size_t nullsize = m_params.T_null; - - while (true) { - unique_lock lock(m_state_mutex); - while (not (m_state == State::NullPrsReady or - m_state == State::Abort)) { - m_state_changed.wait(lock); - } - - if (m_state == State::Abort) { - break; - } - - lock.unlock(); - // We are in NullPrsReady state, and the state will not change now - - // Take the NULL symbol from that frame, but skip the cyclic prefix and - // truncate - size_t null_skip = nullsize - spacing; - - if (m_null.size() != nullsize) { - throw out_of_range("NULL length: " + to_string(m_prs.size()) + - " vs " + to_string(nullsize)); - } - copy(m_null.begin() + null_skip, m_null.begin() + null_skip + spacing, - m_fft_null.getVector()); - m_fft_null.do_FFT(); - - // The phase reference symbol, assume cyclic prefix absent - if (m_prs.size() < spacing) { - throw out_of_range("PRS length: " + to_string(m_prs.size()) + - " vs " + to_string(spacing)); - } - copy(m_prs.begin(), m_prs.begin() + spacing, m_fft_prs.getVector()); - m_fft_prs.do_FFT(); - - /* In TM1, the carriers repeat four times: - * [-768, -384[ - * [-384, 0[ - * ]0, 384] - * ]384, 768] - * A consequence of the fact that the 0 bin is never used is that the - * first carrier of each pair is even for negative k, odd for positive k - * - * We multiply the first carrier of the pair with the conjugate of the second - * carrier in the pair. As they have the same phase, this will make them - * correlate, whereas noise will not correlate. Also, we accumulate the - * measurements over the four blocks. - */ - vector blocks_multiplied(192); - vector prs_power_sq(192); - - /* Equivalent numpy code - blocks = [null_fft[-768:-384], null_fft[-384:], null_fft[1:385], null_fft[385:769]] - blocks_multiplied = np.zeros(384//2, dtype=np.complex128) - for block in blocks: - even_odd = block.reshape(-1, 2) - b = even_odd[...,0] * np.conj(even_odd[...,1]) - blocks_multiplied += b - */ - - for (size_t i = 0; i < 192; i++) { - const complexf *p = m_fft_prs.getVector(); - prs_power_sq[i] = norm(p[1 + 2*i]); - } - - const size_t k_start[] = {2048 - 768, 2048 - 384, 1, 385}; - const complexf *n = m_fft_null.getVector(); - for (size_t k : k_start) { - for (size_t i = 0; i < 192; i++) { - // The two consecutive carriers should have the - // same phase. By multiplying with the conjugate, - // we should get a value with low imaginary component. - // In terms of units, this resembles a norm. - blocks_multiplied[i] += n[k+2*i] * conj(n[k+2*i+1]); - } - } - - auto ix_to_k = [](int ix) -> carrier_t { - if (ix <= 1024) - return ix; - else - return ix - 2048; }; - - const float threshold_factor = 0.4f; - - vector carriers; - for (size_t i = 0; i < 192; i++) { - const float threshold = prs_power_sq[i] * threshold_factor; - if (abs(blocks_multiplied[i]) > threshold) { - // Convert back from "pair index" to k - const carrier_t k = ix_to_k(i*2 + 1); - carriers.push_back(k); - } - } - - unordered_map cp_count; - for (const carrier_t k : carriers) { - if (m_cp_per_carrier.count(k)) { - for (const auto& cps : m_cp_per_carrier[k]) { - cp_count[cps]++; - } - } - } - - size_t num_likely_cps = 0; - for (const auto& cp : cp_count) { - if (cp.second >= 4) { - num_likely_cps++; - } - } - - // Sometimes the number of likely CPs is huge because - // the threshold is wrong. Skip these cases. - if (num_likely_cps < 10) { - for (const auto& cp : cp_count) { - if (cp.second >= 4) { - analyse_phase(cp.first); - } - } - } - - lock.lock(); - m_state = State::Idle; - lock.unlock(); - } -} - -void TIIDecoder::analyse_phase(const CombPattern& cp) -{ - const auto carriers = cp.generateCarriers(); - - const complexf *n = m_fft_null.getVector(); - const complexf *p = m_fft_prs.getVector(); - - auto k_to_ix = [](carrier_t k) -> int { - if (k < 0) - return 2048 + k; - else - return k; }; - - // Both TII carriers take the phase from the first PRS frequency of the pair. - // This assumes carriers is sorted. - vector phases_prs(carriers.size()); - - for (size_t i = 0; i < carriers.size(); i += 2) { - const int ix = k_to_ix(carriers[i]); - - phases_prs[i] = arg(p[ix]); - phases_prs[i+1] = arg(p[ix]); - } - - auto& meas = m_error_per_correction[cp]; - - for (int err = -4; err < 500; err++) { - float abs_err = 0; - - for (size_t j = 0; j < carriers.size(); j++) { - const int ix = k_to_ix(carriers[j]); - constexpr float pi = M_PI; - complexf rotator = polar(1.0f, 2.0f * pi * err * carriers[j] / 2048.0f); - float delta = arg(n[ix] * rotator) - phases_prs[j]; - abs_err += abs(delta); - } - meas.error_per_correction[err] += abs_err; - } - - meas.num_measurements++; - - if (meas.num_measurements >= 5) { - auto best = min_element( - meas.error_per_correction.begin(), - meas.error_per_correction.end(), - [](const pair& lhs, - const pair& rhs) { - return lhs.second < rhs.second; - }); - - if (best != meas.error_per_correction.end()) { - tii_measurement_t m; - m.error = best->second; - m.delay_samples = best->first; - m.comb = cp.comb; - m.pattern = cp.pattern; - - m_radioInterface.onTIIMeasurement(move(m)); - } - - meas.error_per_correction.clear(); - meas.num_measurements = 0; - } -} - +/* + * Copyright (C) 2018 + * Matthias P. Braendli (matthias.braendli@mpb.li) + * + * This file is part of the welle.io. + * + * welle.io is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * welle.io 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with welle.io; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include "tii-decoder.h" + +#ifdef _WINDOWS +#define _USE_MATH_DEFINES +#include +#endif + +using namespace std; + +static const int tii_pattern[70][8] = { // {{{ + {0,0,0,0,1,1,1,1}, + {0,0,0,1,0,1,1,1}, + {0,0,0,1,1,0,1,1}, + {0,0,0,1,1,1,0,1}, + {0,0,0,1,1,1,1,0}, + {0,0,1,0,0,1,1,1}, + {0,0,1,0,1,0,1,1}, + {0,0,1,0,1,1,0,1}, + {0,0,1,0,1,1,1,0}, + {0,0,1,1,0,0,1,1}, + {0,0,1,1,0,1,0,1}, + {0,0,1,1,0,1,1,0}, + {0,0,1,1,1,0,0,1}, + {0,0,1,1,1,0,1,0}, + {0,0,1,1,1,1,0,0}, + {0,1,0,0,0,1,1,1}, + {0,1,0,0,1,0,1,1}, + {0,1,0,0,1,1,0,1}, + {0,1,0,0,1,1,1,0}, + {0,1,0,1,0,0,1,1}, + {0,1,0,1,0,1,0,1}, + {0,1,0,1,0,1,1,0}, + {0,1,0,1,1,0,0,1}, + {0,1,0,1,1,0,1,0}, + {0,1,0,1,1,1,0,0}, + {0,1,1,0,0,0,1,1}, + {0,1,1,0,0,1,0,1}, + {0,1,1,0,0,1,1,0}, + {0,1,1,0,1,0,0,1}, + {0,1,1,0,1,0,1,0}, + {0,1,1,0,1,1,0,0}, + {0,1,1,1,0,0,0,1}, + {0,1,1,1,0,0,1,0}, + {0,1,1,1,0,1,0,0}, + {0,1,1,1,1,0,0,0}, + {1,0,0,0,0,1,1,1}, + {1,0,0,0,1,0,1,1}, + {1,0,0,0,1,1,0,1}, + {1,0,0,0,1,1,1,0}, + {1,0,0,1,0,0,1,1}, + {1,0,0,1,0,1,0,1}, + {1,0,0,1,0,1,1,0}, + {1,0,0,1,1,0,0,1}, + {1,0,0,1,1,0,1,0}, + {1,0,0,1,1,1,0,0}, + {1,0,1,0,0,0,1,1}, + {1,0,1,0,0,1,0,1}, + {1,0,1,0,0,1,1,0}, + {1,0,1,0,1,0,0,1}, + {1,0,1,0,1,0,1,0}, + {1,0,1,0,1,1,0,0}, + {1,0,1,1,0,0,0,1}, + {1,0,1,1,0,0,1,0}, + {1,0,1,1,0,1,0,0}, + {1,0,1,1,1,0,0,0}, + {1,1,0,0,0,0,1,1}, + {1,1,0,0,0,1,0,1}, + {1,1,0,0,0,1,1,0}, + {1,1,0,0,1,0,0,1}, + {1,1,0,0,1,0,1,0}, + {1,1,0,0,1,1,0,0}, + {1,1,0,1,0,0,0,1}, + {1,1,0,1,0,0,1,0}, + {1,1,0,1,0,1,0,0}, + {1,1,0,1,1,0,0,0}, + {1,1,1,0,0,0,0,1}, + {1,1,1,0,0,0,1,0}, + {1,1,1,0,0,1,0,0}, + {1,1,1,0,1,0,0,0}, + {1,1,1,1,0,0,0,0} }; // }}} + +bool operator==(const CombPattern& lhs, const CombPattern& rhs) +{ + return lhs.comb == rhs.comb and lhs.pattern == rhs.pattern; +} + +std::vector CombPattern::generateCarriers() const +{ + std::vector carriers; + carriers.reserve(32); + + for (carrier_t k = 0; k < 384; k++) { + for (int b = 0; b < 8; b++) { + if (k == 1 + 2*comb + 48*b and tii_pattern[pattern][b]) { + carriers.push_back(k - 769); + carriers.push_back(k - 769 + 1); + carriers.push_back(k - 385); + carriers.push_back(k - 385 + 1); + carriers.push_back(k); + carriers.push_back(k + 1); + carriers.push_back(k + 384); + carriers.push_back(k + 384 + 1); + } + } + } + + sort(carriers.begin(), carriers.end()); + + return carriers; +} + +float tii_measurement_t::getDelayKm(void) const +{ + constexpr float km_per_sample = 3e8f / 1000.0f / 2048000.0f; + return delay_samples * km_per_sample; +} + +TIIDecoder::TIIDecoder(const DABParams& params, RadioControllerInterface& ri) : + m_radioInterface(ri), + m_params(params), + m_fft_null(params.T_u), + m_fft_prs(params.T_u) +{ + if (m_params.dabMode != 1) { + clog << "TII decoder does not support mode " << m_params.dabMode << endl; + return; + } + + for (int c = 0; c < 24; c++) { + for (int p = 0; p < 70; p++) { + for (carrier_t k = 0; k < 384; k++) { + for (int b = 0; b < 8; b++) { + if (k == 1 + 2*c + 48*b and tii_pattern[p][b]) { + m_cp_per_carrier[k].emplace(c, p); + } + } + } + } + } + + m_thread = thread(&TIIDecoder::run, this); +} + +TIIDecoder::~TIIDecoder() +{ + unique_lock lock(m_state_mutex); + m_state = State::Abort; + lock.unlock(); + m_state_changed.notify_all(); + + if (m_thread.joinable()) { + m_thread.join(); + } +} + +void TIIDecoder::pushSymbols( + const std::vector& null, + const std::vector& prs) +{ + unique_lock lock(m_state_mutex); + if (m_state == State::Idle) { + m_prs = prs; + m_null = null; + m_state = State::NullPrsReady; + } + lock.unlock(); + m_state_changed.notify_all(); +} + +void TIIDecoder::run() +{ + const size_t spacing = m_params.T_u; + const size_t nullsize = m_params.T_null; + + while (true) { + unique_lock lock(m_state_mutex); + while (not (m_state == State::NullPrsReady or + m_state == State::Abort)) { + m_state_changed.wait(lock); + } + + if (m_state == State::Abort) { + break; + } + + lock.unlock(); + // We are in NullPrsReady state, and the state will not change now + + // Take the NULL symbol from that frame, but skip the cyclic prefix and + // truncate + size_t null_skip = nullsize - spacing; + + if (m_null.size() != nullsize) { + throw out_of_range("NULL length: " + to_string(m_prs.size()) + + " vs " + to_string(nullsize)); + } + copy(m_null.begin() + null_skip, m_null.begin() + null_skip + spacing, + m_fft_null.getVector()); + m_fft_null.do_FFT(); + + // The phase reference symbol, assume cyclic prefix absent + if (m_prs.size() < spacing) { + throw out_of_range("PRS length: " + to_string(m_prs.size()) + + " vs " + to_string(spacing)); + } + copy(m_prs.begin(), m_prs.begin() + spacing, m_fft_prs.getVector()); + m_fft_prs.do_FFT(); + + /* In TM1, the carriers repeat four times: + * [-768, -384[ + * [-384, 0[ + * ]0, 384] + * ]384, 768] + * A consequence of the fact that the 0 bin is never used is that the + * first carrier of each pair is even for negative k, odd for positive k + * + * We multiply the first carrier of the pair with the conjugate of the second + * carrier in the pair. As they have the same phase, this will make them + * correlate, whereas noise will not correlate. Also, we accumulate the + * measurements over the four blocks. + */ + vector blocks_multiplied(192); + vector prs_power_sq(192); + + /* Equivalent numpy code + blocks = [null_fft[-768:-384], null_fft[-384:], null_fft[1:385], null_fft[385:769]] + blocks_multiplied = np.zeros(384//2, dtype=np.complex128) + for block in blocks: + even_odd = block.reshape(-1, 2) + b = even_odd[...,0] * np.conj(even_odd[...,1]) + blocks_multiplied += b + */ + + for (size_t i = 0; i < 192; i++) { + const complexf *p = m_fft_prs.getVector(); + prs_power_sq[i] = norm(p[1 + 2*i]); + } + + const size_t k_start[] = {2048 - 768, 2048 - 384, 1, 385}; + const complexf *n = m_fft_null.getVector(); + for (size_t k : k_start) { + for (size_t i = 0; i < 192; i++) { + // The two consecutive carriers should have the + // same phase. By multiplying with the conjugate, + // we should get a value with low imaginary component. + // In terms of units, this resembles a norm. + blocks_multiplied[i] += n[k+2*i] * conj(n[k+2*i+1]); + } + } + + auto ix_to_k = [](int ix) -> carrier_t { + if (ix <= 1024) + return ix; + else + return ix - 2048; }; + + const float threshold_factor = 0.4f; + + vector carriers; + for (size_t i = 0; i < 192; i++) { + const float threshold = prs_power_sq[i] * threshold_factor; + if (abs(blocks_multiplied[i]) > threshold) { + // Convert back from "pair index" to k + const carrier_t k = ix_to_k(i*2 + 1); + carriers.push_back(k); + } + } + + unordered_map cp_count; + for (const carrier_t k : carriers) { + if (m_cp_per_carrier.count(k)) { + for (const auto& cps : m_cp_per_carrier[k]) { + cp_count[cps]++; + } + } + } + + size_t num_likely_cps = 0; + for (const auto& cp : cp_count) { + if (cp.second >= 4) { + num_likely_cps++; + } + } + + // Sometimes the number of likely CPs is huge because + // the threshold is wrong. Skip these cases. + if (num_likely_cps < 10) { + for (const auto& cp : cp_count) { + if (cp.second >= 4) { + analyse_phase(cp.first); + } + } + } + + lock.lock(); + m_state = State::Idle; + lock.unlock(); + } +} + +void TIIDecoder::analyse_phase(const CombPattern& cp) +{ + const auto carriers = cp.generateCarriers(); + + const complexf *n = m_fft_null.getVector(); + const complexf *p = m_fft_prs.getVector(); + + auto k_to_ix = [](carrier_t k) -> int { + if (k < 0) + return 2048 + k; + else + return k; }; + + // Both TII carriers take the phase from the first PRS frequency of the pair. + // This assumes carriers is sorted. + vector phases_prs(carriers.size()); + + for (size_t i = 0; i < carriers.size(); i += 2) { + const int ix = k_to_ix(carriers[i]); + + phases_prs[i] = arg(p[ix]); + phases_prs[i+1] = arg(p[ix]); + } + + auto& meas = m_error_per_correction[cp]; + + for (int err = -4; err < 500; err++) { + float abs_err = 0; + + for (size_t j = 0; j < carriers.size(); j++) { + const int ix = k_to_ix(carriers[j]); + constexpr float pi = M_PI; + complexf rotator = polar(1.0f, 2.0f * pi * err * carriers[j] / 2048.0f); + float delta = arg(n[ix] * rotator) - phases_prs[j]; + abs_err += abs(delta); + } + meas.error_per_correction[err] += abs_err; + } + + meas.num_measurements++; + + if (meas.num_measurements >= 5) { + auto best = min_element( + meas.error_per_correction.begin(), + meas.error_per_correction.end(), + [](const pair& lhs, + const pair& rhs) { + return lhs.second < rhs.second; + }); + + if (best != meas.error_per_correction.end()) { + tii_measurement_t m; + m.error = best->second; + m.delay_samples = best->first; + m.comb = cp.comb; + m.pattern = cp.pattern; + + m_radioInterface.onTIIMeasurement(move(m)); + } + + meas.error_per_correction.clear(); + meas.num_measurements = 0; + } +} + diff --git a/src/dabdsp/tii-decoder.h b/src/dabdsp/tii-decoder.h index 2f69a44..19d9e87 100644 --- a/src/dabdsp/tii-decoder.h +++ b/src/dabdsp/tii-decoder.h @@ -1,106 +1,106 @@ -/* - * Copyright (C) 2018 - * Matthias P. Braendli (matthias.braendli@mpb.li) - * - * This file is part of the welle.io. - * - * welle.io is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * welle.io 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with welle.io; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ -#include -#include "dab-constants.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include "fft.h" -#include "radio-controller.h" - -using complexf = std::complex; - -// We use this to distinguish between carriers k as given in the spec -// (-768 to 768) and FFT bins (0 to 2048) -using carrier_t = int; - -struct CombPattern { - CombPattern() = default; - CombPattern(int c, int p) : - comb(c), pattern(p) {} - int comb = 0; // From 0 to 24 - int pattern = 0; // From 0 to 70 - - std::vector generateCarriers(void) const; -}; - -// Make CombPattern satisfy Hash and Compare -bool operator==(const CombPattern& lhs, const CombPattern& rhs); - -namespace std { - template<> struct hash { - typedef CombPattern argument_type; - typedef std::size_t result_type; - result_type operator()(argument_type const& cp) const noexcept - { - return cp.comb * 100 + cp.pattern; - } - }; -} - -class TIIDecoder { - public: - TIIDecoder(const DABParams& params, RadioControllerInterface& ri); - ~TIIDecoder(); - TIIDecoder(const TIIDecoder& other) = delete; - TIIDecoder& operator=(const TIIDecoder& other) = delete; - - void pushSymbols( - const std::vector& null, - const std::vector& prs); - - private: - void run(void); - void analyse_phase(const CombPattern& cp); - - RadioControllerInterface& m_radioInterface; - const DABParams& m_params; - - std::vector m_null; - std::vector m_prs; - - std::unordered_map > - m_cp_per_carrier; - - enum class State { Idle, NullPrsReady, Abort }; - - std::thread m_thread; - std::mutex m_state_mutex; - std::condition_variable m_state_changed; - State m_state = State::Idle; - - fft::Forward m_fft_null; - fft::Forward m_fft_prs; - - struct cp_error_measurement_t { - std::unordered_map error_per_correction; - size_t num_measurements = 0; - }; - - std::unordered_map m_error_per_correction; -}; - +/* + * Copyright (C) 2018 + * Matthias P. Braendli (matthias.braendli@mpb.li) + * + * This file is part of the welle.io. + * + * welle.io is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * welle.io 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with welle.io; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include "dab-constants.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "fft.h" +#include "radio-controller.h" + +using complexf = std::complex; + +// We use this to distinguish between carriers k as given in the spec +// (-768 to 768) and FFT bins (0 to 2048) +using carrier_t = int; + +struct CombPattern { + CombPattern() = default; + CombPattern(int c, int p) : + comb(c), pattern(p) {} + int comb = 0; // From 0 to 24 + int pattern = 0; // From 0 to 70 + + std::vector generateCarriers(void) const; +}; + +// Make CombPattern satisfy Hash and Compare +bool operator==(const CombPattern& lhs, const CombPattern& rhs); + +namespace std { + template<> struct hash { + typedef CombPattern argument_type; + typedef std::size_t result_type; + result_type operator()(argument_type const& cp) const noexcept + { + return cp.comb * 100 + cp.pattern; + } + }; +} + +class TIIDecoder { + public: + TIIDecoder(const DABParams& params, RadioControllerInterface& ri); + ~TIIDecoder(); + TIIDecoder(const TIIDecoder& other) = delete; + TIIDecoder& operator=(const TIIDecoder& other) = delete; + + void pushSymbols( + const std::vector& null, + const std::vector& prs); + + private: + void run(void); + void analyse_phase(const CombPattern& cp); + + RadioControllerInterface& m_radioInterface; + const DABParams& m_params; + + std::vector m_null; + std::vector m_prs; + + std::unordered_map > + m_cp_per_carrier; + + enum class State { Idle, NullPrsReady, Abort }; + + std::thread m_thread; + std::mutex m_state_mutex; + std::condition_variable m_state_changed; + State m_state = State::Idle; + + fft::Forward m_fft_null; + fft::Forward m_fft_prs; + + struct cp_error_measurement_t { + std::unordered_map error_per_correction; + size_t num_measurements = 0; + }; + + std::unordered_map m_error_per_correction; +}; + diff --git a/src/dabdsp/uep-protection.cpp b/src/dabdsp/uep-protection.cpp index b6a5a2a..1fd845f 100644 --- a/src/dabdsp/uep-protection.cpp +++ b/src/dabdsp/uep-protection.cpp @@ -1,240 +1,240 @@ -/* - * Copyright (C) 2013 - * Jan van Katwijk (J.vanKatwijk@gmail.com) - * Lazy Chair Programming - * - * This file is part of the SDR-J (JSDR). - * SDR-J is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * SDR-J 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with SDR-J; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - * The deconvolution for both uep and eep - */ -#include "dab-constants.h" -#include "uep-protection.h" -#include "protTables.h" - -struct protectionProfile { - int16_t bitRate; - int16_t protLevel; - int16_t L1; - int16_t L2; - int16_t L3; - int16_t L4; - int16_t PI1; - int16_t PI2; - int16_t PI3; - int16_t PI4; -} profileTable[] = { - {32, 5, 3, 4, 17, 0, 5, 3, 2, -1}, - {32, 4, 3, 3, 18, 0, 11, 6, 5, -1}, - {32, 3, 3, 4, 14, 3, 15, 9, 6, 8}, - {32, 2, 3, 4, 14, 3, 22, 13, 8, 13}, - {32, 1, 3, 5, 13, 3, 24, 17, 12, 17}, - - {48, 5, 4, 3, 26, 3, 5, 4, 2, 3}, - {48, 4, 3, 4, 26, 3, 9, 6, 4, 6}, - {48, 3, 3, 4, 26, 3, 15, 10, 6, 9}, - {48, 2, 3, 4, 26, 3, 24, 14, 8, 15}, - {48, 1, 3, 5, 25, 3, 24, 18, 13, 18}, - - {56, 5, 6, 10, 23, 3, 5, 4, 2, 3}, - {56, 4, 6, 10, 23, 3, 9, 6, 4, 5}, - {56, 3, 6, 12, 21, 3, 16, 7, 6, 9}, - {56, 2, 6, 10, 23, 3, 23, 13, 8, 13}, - - {64, 5, 6, 9, 31, 2, 5, 3, 2, 3}, - {64, 4, 6, 9, 33, 0, 11, 6, 5, -1}, - {64, 3, 6, 12, 27, 3, 16, 8, 6, 9}, - {64, 2, 6, 10, 29, 3, 23, 13, 8, 13}, - {64, 1, 6, 11, 28, 3, 24, 18, 12, 18}, - - {80, 5, 6, 10, 41, 3, 6, 3, 2, 3}, - {80, 4, 6, 10, 41, 3, 11, 6, 5, 6}, - {80, 3, 6, 11, 40, 3, 16, 8, 6, 7}, - {80, 2, 6, 10, 41, 3, 23, 13, 8, 13}, - {80, 1, 6, 10, 41, 3, 24, 7, 12, 18}, - - {96, 5, 7, 9, 53, 3, 5, 4, 2, 4}, - {96, 4, 7, 10, 52, 3, 9, 6, 4, 6}, - {96, 3, 6, 12, 51, 3, 16, 9, 6, 10}, - {96, 2, 6, 10, 53, 3, 22, 12, 9, 12}, - {96, 1, 6, 13, 50, 3, 24, 18, 13, 19}, -// -// Thanks to Kalle Riis, who found that the "112" was missing - {112, 5, 14, 17, 50, 3, 5, 4, 2, 5}, - {112, 4, 11, 21, 49, 3, 9, 6, 4, 8}, - {112, 3, 11, 23, 47, 3, 16, 8, 6, 9}, - {112, 2, 11, 21, 49, 3, 23, 12, 9, 14}, - - {128, 5, 12, 19, 62, 3, 5, 3, 2, 4}, - {128, 4, 11, 21, 61, 3, 11, 6, 5, 7}, - {128, 3, 11, 22, 60, 3, 16, 9, 6, 10}, - {128, 2, 11, 21, 61, 3, 22, 12, 9, 14}, - {128, 1, 11, 20, 62, 3, 24, 17, 13, 19}, - - {160, 5, 11, 19, 87, 3, 5, 4, 2, 4}, - {160, 4, 11, 23, 83, 3, 11, 6, 5, 9}, - {160, 3, 11, 24, 82, 3, 16, 8, 6, 11}, - {160, 2, 11, 21, 85, 3, 22, 11, 9, 13}, - {160, 1, 11, 22, 84, 3, 24, 18, 12, 19}, - - {192, 5, 11, 20, 110, 3, 6, 4, 2, 5}, - {192, 4, 11, 22, 108, 3, 10, 6, 4, 9}, - {192, 3, 11, 24, 106, 3, 16, 10, 6, 11}, - {192, 2, 11, 20, 110, 3, 22, 13, 9, 13}, - {192, 1, 11, 21, 109, 3, 24, 20, 13, 24}, - - {224, 5, 12, 22, 131, 3, 8, 6, 2, 6}, - {224, 4, 12, 26, 127, 3, 12, 8, 4, 11}, - {224, 3, 11, 20, 134, 3, 16, 10, 7, 9}, - {224, 2, 11, 22, 132, 3, 24, 16, 10, 15}, - {224, 1, 11, 24, 130, 3, 24, 20, 12, 20}, - - {256, 5, 11, 24, 154, 3, 6, 5, 2, 5}, - {256, 4, 11, 24, 154, 3, 12, 9, 5, 10}, - {256, 3, 11, 27, 151, 3, 16, 10, 7, 10}, - {256, 2, 11, 22, 156, 3, 24, 14, 10, 13}, - {256, 1, 11, 26, 152, 3, 24, 19, 14, 18}, - - {320, 5, 11, 26, 200, 3, 8, 5, 2, 6}, - {320, 4, 11, 25, 201, 3, 13, 9, 5, 10}, - {320, 2, 11, 26, 200, 3, 24, 17, 9, 17}, - - {384, 5, 11, 27, 247, 3, 8, 6, 2, 7}, - {384, 3, 11, 24, 250, 3, 16, 9, 7, 10}, - {384, 1, 12, 28, 245, 3, 24, 20, 14, 23}, - {0, -1, -1, -1, -1, -1, -1, -1, -1, -1} -}; - -static -int16_t findIndex(int16_t bitRate, int16_t protLevel) -{ - int16_t i; - - for (i = 0; profileTable[i].bitRate != 0; i ++) { - if ( (profileTable[i].bitRate == bitRate) && - (profileTable[i].protLevel == protLevel)) { - return i; - } - } - - return -1; -} - -/** - * the table is based on chapter 11 of the DAB standard. - * - * \brief uep_deconvolve - * - * The bitRate and the protectionLevel determine the - * depuncturing scheme. - */ -UEPProtection::UEPProtection( - int16_t bitRate, - int16_t protLevel) : - Viterbi(24 * bitRate), - outSize(24 * bitRate), - viterbiBlock(outSize * 4 + 24) -{ - int16_t index = findIndex (bitRate, protLevel); - if (index == -1) { - fprintf(stderr, "UEP: %d (%d) has a problem\n", bitRate, protLevel); - index = 1; - } - L1 = profileTable[index].L1; - L2 = profileTable[index].L2; - L3 = profileTable[index].L3; - L4 = profileTable[index].L4; - - PI1 = getPCodes(profileTable[index].PI1 -1); - PI2 = getPCodes(profileTable[index].PI2 -1); - PI3 = getPCodes(profileTable[index].PI3 -1); - if ((profileTable[index].PI4 - 1) != -1) - PI4 = getPCodes(profileTable[index].PI4 -1); - else - PI4 = nullptr; -} - -bool UEPProtection::deconvolve(const softbit_t *v, int32_t size, uint8_t *outBuffer) -{ - int16_t i, j; - int16_t inputCounter = 0; - int32_t viterbiCounter = 0; - (void)size; // currently unused - - // according to the standard we process the logical frame - // with a pair of tuples - // (L1, PI1), (L2, PI2), (L3, PI3), (L4, PI4) - - /// clear the bits in the viterbiBlock, - /// only the non-punctured ones are set - memset(viterbiBlock.data(), 0, (outSize * 4 + 24) * sizeof(softbit_t)); - - for (i = 0; i < L1; i ++) { - for (j = 0; j < 128; j ++) { - if (PI1[j % 32] != 0) { - viterbiBlock[viterbiCounter] = v[inputCounter ++]; - } - viterbiCounter++; - } - } - - for (i = 0; i < L2; i ++) { - for (j = 0; j < 128; j ++) { - if (PI2[j % 32] != 0) { - viterbiBlock[viterbiCounter] = v[inputCounter ++]; - } - viterbiCounter++; - } - } - - for (i = 0; i < L3; i ++) { - for (j = 0; j < 128; j ++) { - if (PI3[j % 32] != 0) { - viterbiBlock[viterbiCounter] = v[inputCounter ++]; - } - viterbiCounter++; - } - } - - for (i = 0; i < L4; i ++) { - if (PI4 == nullptr) { - throw std::logic_error("Invalid usage of NULL PI4"); - } - - for (j = 0; j < 128; j ++) { - if (PI4[j % 32] != 0) { - viterbiBlock[viterbiCounter] = v[inputCounter ++]; - } - viterbiCounter++; - } - } - - /** - * we have a final block of 24 bits with puncturing according to PI_X - * This block constitutes the 6 * 4 bits of the register itself. - */ - for (i = 0; i < 24; i ++) { - if (PI_X[i] != 0) { - viterbiBlock[viterbiCounter] = v[inputCounter ++]; - } - viterbiCounter++; - } - - /// The actual deconvolution is done by the viterbi decoder - - Viterbi::deconvolve(viterbiBlock.data(), outBuffer); - return true; -} - +/* + * Copyright (C) 2013 + * Jan van Katwijk (J.vanKatwijk@gmail.com) + * Lazy Chair Programming + * + * This file is part of the SDR-J (JSDR). + * SDR-J is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * SDR-J 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with SDR-J; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * The deconvolution for both uep and eep + */ +#include "dab-constants.h" +#include "uep-protection.h" +#include "protTables.h" + +struct protectionProfile { + int16_t bitRate; + int16_t protLevel; + int16_t L1; + int16_t L2; + int16_t L3; + int16_t L4; + int16_t PI1; + int16_t PI2; + int16_t PI3; + int16_t PI4; +} profileTable[] = { + {32, 5, 3, 4, 17, 0, 5, 3, 2, -1}, + {32, 4, 3, 3, 18, 0, 11, 6, 5, -1}, + {32, 3, 3, 4, 14, 3, 15, 9, 6, 8}, + {32, 2, 3, 4, 14, 3, 22, 13, 8, 13}, + {32, 1, 3, 5, 13, 3, 24, 17, 12, 17}, + + {48, 5, 4, 3, 26, 3, 5, 4, 2, 3}, + {48, 4, 3, 4, 26, 3, 9, 6, 4, 6}, + {48, 3, 3, 4, 26, 3, 15, 10, 6, 9}, + {48, 2, 3, 4, 26, 3, 24, 14, 8, 15}, + {48, 1, 3, 5, 25, 3, 24, 18, 13, 18}, + + {56, 5, 6, 10, 23, 3, 5, 4, 2, 3}, + {56, 4, 6, 10, 23, 3, 9, 6, 4, 5}, + {56, 3, 6, 12, 21, 3, 16, 7, 6, 9}, + {56, 2, 6, 10, 23, 3, 23, 13, 8, 13}, + + {64, 5, 6, 9, 31, 2, 5, 3, 2, 3}, + {64, 4, 6, 9, 33, 0, 11, 6, 5, -1}, + {64, 3, 6, 12, 27, 3, 16, 8, 6, 9}, + {64, 2, 6, 10, 29, 3, 23, 13, 8, 13}, + {64, 1, 6, 11, 28, 3, 24, 18, 12, 18}, + + {80, 5, 6, 10, 41, 3, 6, 3, 2, 3}, + {80, 4, 6, 10, 41, 3, 11, 6, 5, 6}, + {80, 3, 6, 11, 40, 3, 16, 8, 6, 7}, + {80, 2, 6, 10, 41, 3, 23, 13, 8, 13}, + {80, 1, 6, 10, 41, 3, 24, 7, 12, 18}, + + {96, 5, 7, 9, 53, 3, 5, 4, 2, 4}, + {96, 4, 7, 10, 52, 3, 9, 6, 4, 6}, + {96, 3, 6, 12, 51, 3, 16, 9, 6, 10}, + {96, 2, 6, 10, 53, 3, 22, 12, 9, 12}, + {96, 1, 6, 13, 50, 3, 24, 18, 13, 19}, +// +// Thanks to Kalle Riis, who found that the "112" was missing + {112, 5, 14, 17, 50, 3, 5, 4, 2, 5}, + {112, 4, 11, 21, 49, 3, 9, 6, 4, 8}, + {112, 3, 11, 23, 47, 3, 16, 8, 6, 9}, + {112, 2, 11, 21, 49, 3, 23, 12, 9, 14}, + + {128, 5, 12, 19, 62, 3, 5, 3, 2, 4}, + {128, 4, 11, 21, 61, 3, 11, 6, 5, 7}, + {128, 3, 11, 22, 60, 3, 16, 9, 6, 10}, + {128, 2, 11, 21, 61, 3, 22, 12, 9, 14}, + {128, 1, 11, 20, 62, 3, 24, 17, 13, 19}, + + {160, 5, 11, 19, 87, 3, 5, 4, 2, 4}, + {160, 4, 11, 23, 83, 3, 11, 6, 5, 9}, + {160, 3, 11, 24, 82, 3, 16, 8, 6, 11}, + {160, 2, 11, 21, 85, 3, 22, 11, 9, 13}, + {160, 1, 11, 22, 84, 3, 24, 18, 12, 19}, + + {192, 5, 11, 20, 110, 3, 6, 4, 2, 5}, + {192, 4, 11, 22, 108, 3, 10, 6, 4, 9}, + {192, 3, 11, 24, 106, 3, 16, 10, 6, 11}, + {192, 2, 11, 20, 110, 3, 22, 13, 9, 13}, + {192, 1, 11, 21, 109, 3, 24, 20, 13, 24}, + + {224, 5, 12, 22, 131, 3, 8, 6, 2, 6}, + {224, 4, 12, 26, 127, 3, 12, 8, 4, 11}, + {224, 3, 11, 20, 134, 3, 16, 10, 7, 9}, + {224, 2, 11, 22, 132, 3, 24, 16, 10, 15}, + {224, 1, 11, 24, 130, 3, 24, 20, 12, 20}, + + {256, 5, 11, 24, 154, 3, 6, 5, 2, 5}, + {256, 4, 11, 24, 154, 3, 12, 9, 5, 10}, + {256, 3, 11, 27, 151, 3, 16, 10, 7, 10}, + {256, 2, 11, 22, 156, 3, 24, 14, 10, 13}, + {256, 1, 11, 26, 152, 3, 24, 19, 14, 18}, + + {320, 5, 11, 26, 200, 3, 8, 5, 2, 6}, + {320, 4, 11, 25, 201, 3, 13, 9, 5, 10}, + {320, 2, 11, 26, 200, 3, 24, 17, 9, 17}, + + {384, 5, 11, 27, 247, 3, 8, 6, 2, 7}, + {384, 3, 11, 24, 250, 3, 16, 9, 7, 10}, + {384, 1, 12, 28, 245, 3, 24, 20, 14, 23}, + {0, -1, -1, -1, -1, -1, -1, -1, -1, -1} +}; + +static +int16_t findIndex(int16_t bitRate, int16_t protLevel) +{ + int16_t i; + + for (i = 0; profileTable[i].bitRate != 0; i ++) { + if ( (profileTable[i].bitRate == bitRate) && + (profileTable[i].protLevel == protLevel)) { + return i; + } + } + + return -1; +} + +/** + * the table is based on chapter 11 of the DAB standard. + * + * \brief uep_deconvolve + * + * The bitRate and the protectionLevel determine the + * depuncturing scheme. + */ +UEPProtection::UEPProtection( + int16_t bitRate, + int16_t protLevel) : + Viterbi(24 * bitRate), + outSize(24 * bitRate), + viterbiBlock(outSize * 4 + 24) +{ + int16_t index = findIndex (bitRate, protLevel); + if (index == -1) { + fprintf(stderr, "UEP: %d (%d) has a problem\n", bitRate, protLevel); + index = 1; + } + L1 = profileTable[index].L1; + L2 = profileTable[index].L2; + L3 = profileTable[index].L3; + L4 = profileTable[index].L4; + + PI1 = getPCodes(profileTable[index].PI1 -1); + PI2 = getPCodes(profileTable[index].PI2 -1); + PI3 = getPCodes(profileTable[index].PI3 -1); + if ((profileTable[index].PI4 - 1) != -1) + PI4 = getPCodes(profileTable[index].PI4 -1); + else + PI4 = nullptr; +} + +bool UEPProtection::deconvolve(const softbit_t *v, int32_t size, uint8_t *outBuffer) +{ + int16_t i, j; + int16_t inputCounter = 0; + int32_t viterbiCounter = 0; + (void)size; // currently unused + + // according to the standard we process the logical frame + // with a pair of tuples + // (L1, PI1), (L2, PI2), (L3, PI3), (L4, PI4) + + /// clear the bits in the viterbiBlock, + /// only the non-punctured ones are set + memset(viterbiBlock.data(), 0, (outSize * 4 + 24) * sizeof(softbit_t)); + + for (i = 0; i < L1; i ++) { + for (j = 0; j < 128; j ++) { + if (PI1[j % 32] != 0) { + viterbiBlock[viterbiCounter] = v[inputCounter ++]; + } + viterbiCounter++; + } + } + + for (i = 0; i < L2; i ++) { + for (j = 0; j < 128; j ++) { + if (PI2[j % 32] != 0) { + viterbiBlock[viterbiCounter] = v[inputCounter ++]; + } + viterbiCounter++; + } + } + + for (i = 0; i < L3; i ++) { + for (j = 0; j < 128; j ++) { + if (PI3[j % 32] != 0) { + viterbiBlock[viterbiCounter] = v[inputCounter ++]; + } + viterbiCounter++; + } + } + + for (i = 0; i < L4; i ++) { + if (PI4 == nullptr) { + throw std::logic_error("Invalid usage of NULL PI4"); + } + + for (j = 0; j < 128; j ++) { + if (PI4[j % 32] != 0) { + viterbiBlock[viterbiCounter] = v[inputCounter ++]; + } + viterbiCounter++; + } + } + + /** + * we have a final block of 24 bits with puncturing according to PI_X + * This block constitutes the 6 * 4 bits of the register itself. + */ + for (i = 0; i < 24; i ++) { + if (PI_X[i] != 0) { + viterbiBlock[viterbiCounter] = v[inputCounter ++]; + } + viterbiCounter++; + } + + /// The actual deconvolution is done by the viterbi decoder + + Viterbi::deconvolve(viterbiBlock.data(), outBuffer); + return true; +} + diff --git a/src/dabdsp/uep-protection.h b/src/dabdsp/uep-protection.h index bbfa0b8..f068de4 100644 --- a/src/dabdsp/uep-protection.h +++ b/src/dabdsp/uep-protection.h @@ -1,50 +1,50 @@ -/* - * Copyright (C) 2013 - * Jan van Katwijk (J.vanKatwijk@gmail.com) - * Lazy Chair Programming - * - * This file is part of the SDR-J (JSDR). - * SDR-J is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * SDR-J 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with SDR-J; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#ifndef UEP_PROTECTION -#define UEP_PROTECTION - -#include -#include -#include -#include "protection.h" -#include "viterbi.h" - -class UEPProtection: public Protection, public Viterbi -{ - public: - UEPProtection(int16_t bitRate, int16_t protLevel); - bool deconvolve(const softbit_t *v, int32_t size, uint8_t *outBuffer); - private: - int16_t L1; - int16_t L2; - int16_t L3; - int16_t L4; - const int8_t *PI1; - const int8_t *PI2; - const int8_t *PI3; - const int8_t *PI4; - int32_t outSize; - std::vector viterbiBlock; -}; - -#endif - +/* + * Copyright (C) 2013 + * Jan van Katwijk (J.vanKatwijk@gmail.com) + * Lazy Chair Programming + * + * This file is part of the SDR-J (JSDR). + * SDR-J is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * SDR-J 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with SDR-J; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef UEP_PROTECTION +#define UEP_PROTECTION + +#include +#include +#include +#include "protection.h" +#include "viterbi.h" + +class UEPProtection: public Protection, public Viterbi +{ + public: + UEPProtection(int16_t bitRate, int16_t protLevel); + bool deconvolve(const softbit_t *v, int32_t size, uint8_t *outBuffer); + private: + int16_t L1; + int16_t L2; + int16_t L3; + int16_t L4; + const int8_t *PI1; + const int8_t *PI2; + const int8_t *PI3; + const int8_t *PI4; + int32_t outSize; + std::vector viterbiBlock; +}; + +#endif + diff --git a/src/dabdsp/viterbi.cpp b/src/dabdsp/viterbi.cpp index c3e56c6..8b1fb86 100644 --- a/src/dabdsp/viterbi.cpp +++ b/src/dabdsp/viterbi.cpp @@ -1,355 +1,355 @@ -/* - * Copyright (C) 2013 - * Jan van Katwijk (J.vanKatwijk@gmail.com) - * Lazy Chair Computing - * - * This file is part of the Qt-DAB - * Qt-DAB is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * Qt-DAB 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Qt-DAB; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ -#include -#include -#include "viterbi.h" -#include - -#ifdef _WINDOWS -# include -# include -# include -#endif - -// It took a while to discover that the polynomes we used -// in our own "straightforward" implementation was bitreversed!! -// The official one is on top. -#define K 7 -#define POLYS { 0155, 0117, 0123, 0155} -//#define POLYS {109, 79, 83, 109} -// In the reversed form the polys look: -//#define POLYS { 0133, 0171, 0145, 0133 } -//#define POLYS { 91, 121, 101, 91 } - -#define METRICSHIFT 0 -#define PRECISIONSHIFT 0 -#define RENORMALIZE_THRESHOLD 137 - -/* ADDSHIFT and SUBSHIFT make sure that the thing returned is a byte. */ -#if (K-1<8) -#define ADDSHIFT (8-(K-1)) -#define SUBSHIFT 0 -#elif (K-1>8) -#define ADDSHIFT 0 -#define SUBSHIFT ((K-1)-8) -#else -#define ADDSHIFT 0 -#define SUBSHIFT 0 -#endif - - -static uint8_t Partab[] = -{ 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, - 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, - 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, - 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, - 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, - 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, - 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, - 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, - 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, - 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, - 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, - 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, - 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, - 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, - 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, - 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0}; - -// One could create the table above, i.e. a 256 entry -// odd-parity lookup table by the following function -// It is now precomputed -void Viterbi::partab_init() -{ - int16_t i,cnt,ti; - - for (i = 0; i < 256; i++){ - cnt = 0; - ti = i; - while (ti != 0) { - if (ti & 1) cnt++; - ti >>= 1; - } - Partab[i] = cnt & 1; - } -} - -int Viterbi::parity(int x) -{ - /* Fold down to one byte */ - x ^= (x >> 16); - x ^= (x >> 8); - return Partab[x]; - // return parityb(x); -} - -static inline -void renormalize(COMPUTETYPE* X, COMPUTETYPE threshold) -{ - int32_t i; - - if (X[0] > threshold) { - COMPUTETYPE min = X[0]; - for (i = 0; i < NUMSTATES; i++) { - if (min > X[i]) - min = X[i]; - } - - for (i = 0; i < NUMSTATES; i++) { - X[i] -= min; - } - } -} - -// The main use of the viterbi decoder is in handling the FIC blocks -// There are (in mode 1) 3 ofdm blocks, giving 4 FIC blocks -// There all have a predefined length. In that case we use the -// "fast" (i.e. spiral) code, otherwise we use the generic code -Viterbi::Viterbi(int16_t wordlength) -{ - int polys[RATE] = POLYS; - int16_t i, state; -#ifdef _WINDOWS - uint32_t size; -#endif - - frameBits = wordlength; - // partab_init (); - - // B I G N O T E The spiral code uses (wordLength + (K - 1) * sizeof ... - // However, the application then crashes, so something is not OK - // By doubling the size, the problem disappears. It is not solved though - // and not further investigation. -#ifdef _WINDOWS - size = 2 * ((wordlength + (K - 1)) / 8 + 1 + 16) & ~0xF; - data = (uint8_t *)_aligned_malloc (size, 16); - size = 2 * (RATE * (wordlength + (K - 1)) * sizeof(COMPUTETYPE) + 16) & ~0xF; - symbols = (COMPUTETYPE *)_aligned_malloc (size, 16); - size = 2 * (wordlength + (K - 1)) * sizeof (decision_t); - size = (size + 16) & ~0xF; - vp. decisions = (decision_t *)_aligned_malloc (size, DECISIONALIGN); -#else - if (posix_memalign ((void**)&data, 16, - (wordlength + (K - 1))/ 8 + 1)){ - printf("Allocation of data array failed\n"); - } - if (posix_memalign ((void**)&symbols, 16, - RATE * (wordlength + (K - 1)) * sizeof(COMPUTETYPE))){ - printf("Allocation of symbols array failed\n"); - } - if (posix_memalign ((void**)&(vp. decisions), - DECISIONALIGN, - 2 * (wordlength + (K - 1)) * sizeof (decision_t))){ - printf ("Allocation of vp decisions failed\n"); - } -#endif - - for (state = 0; state < NUMSTATES / 2; state++) { - for (i = 0; i < RATE; i++) { - Branchtab[i * NUMSTATES / 2 + state] = - (polys[i] < 0) ^ - parity((2 * state) & abs (polys[i])) ? 255 : 0; - } - } - - init_viterbi (&vp, 0); -} - - -Viterbi::~Viterbi() -{ -#ifdef _WINDOWS - _aligned_free (vp. decisions); - _aligned_free (data); - _aligned_free (symbols); -#else - free (vp. decisions); - free (data); - free (symbols); -#endif -} - -static int maskTable[] = {128, 64, 32, 16, 8, 4, 2, 1}; - -static inline -uint8_t getbit (uint8_t v, int32_t o) -{ - return (v & maskTable[o]) ? 1 : 0; -} - -//static -//uint8_t getbit (uint8_t v, int32_t o) { -//uint8_t mask = 1 << (7 - o); -// return (v & mask) ? 1 : 0; -//} - -// depends: POLYS, RATE, COMPUTETYPE -// encode was only used for testing purposes -//void encode (/*const*/ unsigned char *bytes, COMPUTETYPE *symbols, int nbits) { -//int i, k; -//int polys[RATE] = POLYS; -//int sr = 0; -// -//// FIXME: this is slowish -//// -- remember about the padding! -// for (i = 0; i < nbits + (K - 1); i++) { -// int b = bytes[i/8]; -// int j = i % 8; -// int bit = (b >> (7-j)) & 1; -// -// sr = (sr << 1) | bit; -// for (k = 0; k < RATE; k++) -// *(symbols++) = parity(sr & polys[k]); -// } -//} - -// Note that our DAB environment maps the softbits to -127 .. 127 -// we have to map that onto 0 .. 255 - -void Viterbi::deconvolve(softbit_t *input, uint8_t *output) -{ - uint32_t i; - - init_viterbi (&vp, 0); - for (i = 0; i < (uint16_t)(frameBits + (K - 1)) * RATE; i ++) { - int16_t temp = ((int16_t)input[i]) + 127; - if (temp < 0) temp = 0; - if (temp > 255) temp = 255; - symbols[i] = temp; - } - - update_viterbi_blk_GENERIC (&vp, symbols, frameBits + (K - 1)); - - chainback_viterbi (&vp, data, frameBits, 0); - - for (i = 0; i < (uint16_t)frameBits; i ++) - output[i] = getbit (data[i >> 3], i & 07); -} - -/* C-language butterfly */ -void Viterbi::BFLY( - int i, - int s, - COMPUTETYPE * syms, - struct v * vp, - decision_t * d) -{ - int32_t j, decision0, decision1; - COMPUTETYPE metric,m0,m1,m2,m3; - - metric =0; - for (j = 0; j < RATE;j++) - metric += (Branchtab[i + j * NUMSTATES/2] ^ syms[s*RATE+j]) >> - METRICSHIFT ; - metric = metric >> PRECISIONSHIFT; - const COMPUTETYPE max = - ((RATE * ((256 - 1) >> METRICSHIFT)) >> PRECISIONSHIFT); - - m0 = vp->old_metrics->t[i] + metric; - m1 = vp->old_metrics->t[i + NUMSTATES / 2] + (max - metric); - m2 = vp->old_metrics->t[i] + (max - metric); - m3 = vp->old_metrics->t[i + NUMSTATES / 2] + metric; - - decision0 = ((int32_t)(m0 - m1)) > 0; - decision1 = ((int32_t)(m2 - m3)) > 0; - - vp->new_metrics->t[2 * i] = decision0 ? m1 : m0; - vp->new_metrics->t[2 * i + 1] = decision1 ? m3 : m2; - - d->w[i/(sizeof(uint32_t)*8/2)+s*(sizeof(decision_t)/sizeof(uint32_t))] |= - (decision0|decision1<<1) << ((2*i)&(sizeof(uint32_t)*8-1)); -} - -/* Update decoder with a block of demodulated symbols - * Note that nbits is the number of decoded data bits, not the number - * of symbols! - */ -void Viterbi::update_viterbi_blk_GENERIC( - struct v *vp, - COMPUTETYPE *syms, - int16_t nbits) -{ - decision_t *d = (decision_t *)vp->decisions; - int32_t s, i; - - for (s = 0; s < nbits; s++) { - memset (&d[s], 0, sizeof (decision_t)); - } - - for (s = 0; s < nbits; s++) { - void *tmp; - for (i = 0; i < NUMSTATES / 2; i++) { - BFLY (i, s, syms, vp, vp->decisions); - } - - renormalize (vp->new_metrics -> t, RENORMALIZE_THRESHOLD); - // Swap pointers to old and new metrics - tmp = vp->old_metrics; - vp->old_metrics = vp -> new_metrics; - vp->new_metrics = (metric_t *)tmp; - } -} - -// -/* Viterbi chainback */ -void Viterbi::chainback_viterbi( - struct v *vp, - uint8_t *data, /* Decoded output data */ - int16_t nbits, /* Number of data bits */ - uint16_t endstate) /*Terminal encoder state */ -{ - decision_t *d = vp->decisions; - - /* Make room beyond the end of the encoder register so we can - * accumulate a full byte of decoded data - */ - endstate = (endstate % NUMSTATES) << ADDSHIFT; - /* The store into data[] only needs to be done every 8 bits. - * But this avoids a conditional branch, and the writes will - * combine in the cache anyway - */ - d += (K - 1); /* Look past tail */ - while (nbits-- != 0){ - int k; - // int l = (endstate >> ADDSHIFT) / 32; - // int m = (endstate >> ADDSHIFT) % 32; - k = (d[nbits].w[(endstate >> ADDSHIFT) / 32] >> - ((endstate>>ADDSHIFT) % 32)) & 1; - endstate = (endstate >> 1) | (k << (K - 2 + ADDSHIFT)); - data[nbits >> 3] = endstate >> SUBSHIFT; - } -} - -/* Initialize Viterbi decoder for start of new frame */ -void Viterbi::init_viterbi(struct v *p, int16_t starting_state) -{ - struct v *vp = p; - int32_t i; - - for (i = 0; i < NUMSTATES; i++) - vp->metrics1.t[i] = 63; - - vp->old_metrics = &vp -> metrics1; - vp->new_metrics = &vp -> metrics2; - /* Bias known start state */ - vp->old_metrics-> t[starting_state & (NUMSTATES-1)] = 0; -} - +/* + * Copyright (C) 2013 + * Jan van Katwijk (J.vanKatwijk@gmail.com) + * Lazy Chair Computing + * + * This file is part of the Qt-DAB + * Qt-DAB is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Qt-DAB 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Qt-DAB; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include "viterbi.h" +#include + +#ifdef _WINDOWS +# include +# include +# include +#endif + +// It took a while to discover that the polynomes we used +// in our own "straightforward" implementation was bitreversed!! +// The official one is on top. +#define K 7 +#define POLYS { 0155, 0117, 0123, 0155} +//#define POLYS {109, 79, 83, 109} +// In the reversed form the polys look: +//#define POLYS { 0133, 0171, 0145, 0133 } +//#define POLYS { 91, 121, 101, 91 } + +#define METRICSHIFT 0 +#define PRECISIONSHIFT 0 +#define RENORMALIZE_THRESHOLD 137 + +/* ADDSHIFT and SUBSHIFT make sure that the thing returned is a byte. */ +#if (K-1<8) +#define ADDSHIFT (8-(K-1)) +#define SUBSHIFT 0 +#elif (K-1>8) +#define ADDSHIFT 0 +#define SUBSHIFT ((K-1)-8) +#else +#define ADDSHIFT 0 +#define SUBSHIFT 0 +#endif + + +static uint8_t Partab[] = +{ 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0}; + +// One could create the table above, i.e. a 256 entry +// odd-parity lookup table by the following function +// It is now precomputed +void Viterbi::partab_init() +{ + int16_t i,cnt,ti; + + for (i = 0; i < 256; i++){ + cnt = 0; + ti = i; + while (ti != 0) { + if (ti & 1) cnt++; + ti >>= 1; + } + Partab[i] = cnt & 1; + } +} + +int Viterbi::parity(int x) +{ + /* Fold down to one byte */ + x ^= (x >> 16); + x ^= (x >> 8); + return Partab[x]; + // return parityb(x); +} + +static inline +void renormalize(COMPUTETYPE* X, COMPUTETYPE threshold) +{ + int32_t i; + + if (X[0] > threshold) { + COMPUTETYPE min = X[0]; + for (i = 0; i < NUMSTATES; i++) { + if (min > X[i]) + min = X[i]; + } + + for (i = 0; i < NUMSTATES; i++) { + X[i] -= min; + } + } +} + +// The main use of the viterbi decoder is in handling the FIC blocks +// There are (in mode 1) 3 ofdm blocks, giving 4 FIC blocks +// There all have a predefined length. In that case we use the +// "fast" (i.e. spiral) code, otherwise we use the generic code +Viterbi::Viterbi(int16_t wordlength) +{ + int polys[RATE] = POLYS; + int16_t i, state; +#ifdef _WINDOWS + uint32_t size; +#endif + + frameBits = wordlength; + // partab_init (); + + // B I G N O T E The spiral code uses (wordLength + (K - 1) * sizeof ... + // However, the application then crashes, so something is not OK + // By doubling the size, the problem disappears. It is not solved though + // and not further investigation. +#ifdef _WINDOWS + size = 2 * ((wordlength + (K - 1)) / 8 + 1 + 16) & ~0xF; + data = (uint8_t *)_aligned_malloc (size, 16); + size = 2 * (RATE * (wordlength + (K - 1)) * sizeof(COMPUTETYPE) + 16) & ~0xF; + symbols = (COMPUTETYPE *)_aligned_malloc (size, 16); + size = 2 * (wordlength + (K - 1)) * sizeof (decision_t); + size = (size + 16) & ~0xF; + vp. decisions = (decision_t *)_aligned_malloc (size, DECISIONALIGN); +#else + if (posix_memalign ((void**)&data, 16, + (wordlength + (K - 1))/ 8 + 1)){ + printf("Allocation of data array failed\n"); + } + if (posix_memalign ((void**)&symbols, 16, + RATE * (wordlength + (K - 1)) * sizeof(COMPUTETYPE))){ + printf("Allocation of symbols array failed\n"); + } + if (posix_memalign ((void**)&(vp. decisions), + DECISIONALIGN, + 2 * (wordlength + (K - 1)) * sizeof (decision_t))){ + printf ("Allocation of vp decisions failed\n"); + } +#endif + + for (state = 0; state < NUMSTATES / 2; state++) { + for (i = 0; i < RATE; i++) { + Branchtab[i * NUMSTATES / 2 + state] = + (polys[i] < 0) ^ + parity((2 * state) & abs (polys[i])) ? 255 : 0; + } + } + + init_viterbi (&vp, 0); +} + + +Viterbi::~Viterbi() +{ +#ifdef _WINDOWS + _aligned_free (vp. decisions); + _aligned_free (data); + _aligned_free (symbols); +#else + free (vp. decisions); + free (data); + free (symbols); +#endif +} + +static int maskTable[] = {128, 64, 32, 16, 8, 4, 2, 1}; + +static inline +uint8_t getbit (uint8_t v, int32_t o) +{ + return (v & maskTable[o]) ? 1 : 0; +} + +//static +//uint8_t getbit (uint8_t v, int32_t o) { +//uint8_t mask = 1 << (7 - o); +// return (v & mask) ? 1 : 0; +//} + +// depends: POLYS, RATE, COMPUTETYPE +// encode was only used for testing purposes +//void encode (/*const*/ unsigned char *bytes, COMPUTETYPE *symbols, int nbits) { +//int i, k; +//int polys[RATE] = POLYS; +//int sr = 0; +// +//// FIXME: this is slowish +//// -- remember about the padding! +// for (i = 0; i < nbits + (K - 1); i++) { +// int b = bytes[i/8]; +// int j = i % 8; +// int bit = (b >> (7-j)) & 1; +// +// sr = (sr << 1) | bit; +// for (k = 0; k < RATE; k++) +// *(symbols++) = parity(sr & polys[k]); +// } +//} + +// Note that our DAB environment maps the softbits to -127 .. 127 +// we have to map that onto 0 .. 255 + +void Viterbi::deconvolve(softbit_t *input, uint8_t *output) +{ + uint32_t i; + + init_viterbi (&vp, 0); + for (i = 0; i < (uint16_t)(frameBits + (K - 1)) * RATE; i ++) { + int16_t temp = ((int16_t)input[i]) + 127; + if (temp < 0) temp = 0; + if (temp > 255) temp = 255; + symbols[i] = temp; + } + + update_viterbi_blk_GENERIC (&vp, symbols, frameBits + (K - 1)); + + chainback_viterbi (&vp, data, frameBits, 0); + + for (i = 0; i < (uint16_t)frameBits; i ++) + output[i] = getbit (data[i >> 3], i & 07); +} + +/* C-language butterfly */ +void Viterbi::BFLY( + int i, + int s, + COMPUTETYPE * syms, + struct v * vp, + decision_t * d) +{ + int32_t j, decision0, decision1; + COMPUTETYPE metric,m0,m1,m2,m3; + + metric =0; + for (j = 0; j < RATE;j++) + metric += (Branchtab[i + j * NUMSTATES/2] ^ syms[s*RATE+j]) >> + METRICSHIFT ; + metric = metric >> PRECISIONSHIFT; + const COMPUTETYPE max = + ((RATE * ((256 - 1) >> METRICSHIFT)) >> PRECISIONSHIFT); + + m0 = vp->old_metrics->t[i] + metric; + m1 = vp->old_metrics->t[i + NUMSTATES / 2] + (max - metric); + m2 = vp->old_metrics->t[i] + (max - metric); + m3 = vp->old_metrics->t[i + NUMSTATES / 2] + metric; + + decision0 = ((int32_t)(m0 - m1)) > 0; + decision1 = ((int32_t)(m2 - m3)) > 0; + + vp->new_metrics->t[2 * i] = decision0 ? m1 : m0; + vp->new_metrics->t[2 * i + 1] = decision1 ? m3 : m2; + + d->w[i/(sizeof(uint32_t)*8/2)+s*(sizeof(decision_t)/sizeof(uint32_t))] |= + (decision0|decision1<<1) << ((2*i)&(sizeof(uint32_t)*8-1)); +} + +/* Update decoder with a block of demodulated symbols + * Note that nbits is the number of decoded data bits, not the number + * of symbols! + */ +void Viterbi::update_viterbi_blk_GENERIC( + struct v *vp, + COMPUTETYPE *syms, + int16_t nbits) +{ + decision_t *d = (decision_t *)vp->decisions; + int32_t s, i; + + for (s = 0; s < nbits; s++) { + memset (&d[s], 0, sizeof (decision_t)); + } + + for (s = 0; s < nbits; s++) { + void *tmp; + for (i = 0; i < NUMSTATES / 2; i++) { + BFLY (i, s, syms, vp, vp->decisions); + } + + renormalize (vp->new_metrics -> t, RENORMALIZE_THRESHOLD); + // Swap pointers to old and new metrics + tmp = vp->old_metrics; + vp->old_metrics = vp -> new_metrics; + vp->new_metrics = (metric_t *)tmp; + } +} + +// +/* Viterbi chainback */ +void Viterbi::chainback_viterbi( + struct v *vp, + uint8_t *data, /* Decoded output data */ + int16_t nbits, /* Number of data bits */ + uint16_t endstate) /*Terminal encoder state */ +{ + decision_t *d = vp->decisions; + + /* Make room beyond the end of the encoder register so we can + * accumulate a full byte of decoded data + */ + endstate = (endstate % NUMSTATES) << ADDSHIFT; + /* The store into data[] only needs to be done every 8 bits. + * But this avoids a conditional branch, and the writes will + * combine in the cache anyway + */ + d += (K - 1); /* Look past tail */ + while (nbits-- != 0){ + int k; + // int l = (endstate >> ADDSHIFT) / 32; + // int m = (endstate >> ADDSHIFT) % 32; + k = (d[nbits].w[(endstate >> ADDSHIFT) / 32] >> + ((endstate>>ADDSHIFT) % 32)) & 1; + endstate = (endstate >> 1) | (k << (K - 2 + ADDSHIFT)); + data[nbits >> 3] = endstate >> SUBSHIFT; + } +} + +/* Initialize Viterbi decoder for start of new frame */ +void Viterbi::init_viterbi(struct v *p, int16_t starting_state) +{ + struct v *vp = p; + int32_t i; + + for (i = 0; i < NUMSTATES; i++) + vp->metrics1.t[i] = 63; + + vp->old_metrics = &vp -> metrics1; + vp->new_metrics = &vp -> metrics2; + /* Bias known start state */ + vp->old_metrics-> t[starting_state & (NUMSTATES-1)] = 0; +} + diff --git a/src/dabdsp/viterbi.h b/src/dabdsp/viterbi.h index fb063a4..8050be0 100644 --- a/src/dabdsp/viterbi.h +++ b/src/dabdsp/viterbi.h @@ -1,74 +1,74 @@ -#ifndef __VITERBI__ -#define __VITERBI__ -/* - * Viterbi.h according to the SPIRAL project - */ -#include "dab-constants.h" -#include "MathHelper.h" - -// For our particular viterbi decoder, we have -#define RATE 4 -#define NUMSTATES 64 -#define DECISIONTYPE uint32_t -#define DECISIONALIGN 32 -#define DECISIONTYPE_BITSIZE (sizeof(DECISIONTYPE) * 8) -#define COMPUTETYPE uint16_t - -typedef struct { - DECISIONTYPE w[NUMSTATES/32]; -} decision_t; - -typedef union alignas(16) { - COMPUTETYPE t[NUMSTATES]; -} metric_t; - -/* State info for instance of Viterbi decoder -*/ - -struct v { - /* path metric buffer 1 */ - alignas(16) metric_t metrics1; - /* path metric buffer 2 */ - alignas(16) metric_t metrics2; - /* Pointers to path metrics, swapped on every bit */ - metric_t *old_metrics,*new_metrics; - decision_t *decisions; /* decisions */ -}; - -class Viterbi -{ - public: - Viterbi(int16_t); - ~Viterbi(void); - Viterbi(const Viterbi& other) = delete; - Viterbi& operator=(const Viterbi& other) = delete; - void deconvolve(softbit_t *input, uint8_t *output); - - private: - struct v vp; - alignas(16) COMPUTETYPE Branchtab[NUMSTATES / 2 * RATE]; - - // int parityb (uint8_t); - int parity(int x); - void partab_init (void); - // uint8_t Partab [256]; - void init_viterbi(struct v *, int16_t starting_state); - - void update_viterbi_blk_GENERIC( struct v *vp, - COMPUTETYPE *syms, - int16_t nbits); - - void chainback_viterbi( struct v *vp, - uint8_t *data, /* Decoded output data */ - int16_t nbits, /* Number of data bits */ - uint16_t endstate); /*Terminal encoder state */ - - void BFLY( int i, int s, COMPUTETYPE * syms, struct v * vp, decision_t * d); - - uint8_t *data; - COMPUTETYPE *symbols; - int16_t frameBits; -}; - -#endif - +#ifndef __VITERBI__ +#define __VITERBI__ +/* + * Viterbi.h according to the SPIRAL project + */ +#include "dab-constants.h" +#include "MathHelper.h" + +// For our particular viterbi decoder, we have +#define RATE 4 +#define NUMSTATES 64 +#define DECISIONTYPE uint32_t +#define DECISIONALIGN 32 +#define DECISIONTYPE_BITSIZE (sizeof(DECISIONTYPE) * 8) +#define COMPUTETYPE uint16_t + +typedef struct { + DECISIONTYPE w[NUMSTATES/32]; +} decision_t; + +typedef union alignas(16) { + COMPUTETYPE t[NUMSTATES]; +} metric_t; + +/* State info for instance of Viterbi decoder +*/ + +struct v { + /* path metric buffer 1 */ + alignas(16) metric_t metrics1; + /* path metric buffer 2 */ + alignas(16) metric_t metrics2; + /* Pointers to path metrics, swapped on every bit */ + metric_t *old_metrics,*new_metrics; + decision_t *decisions; /* decisions */ +}; + +class Viterbi +{ + public: + Viterbi(int16_t); + ~Viterbi(void); + Viterbi(const Viterbi& other) = delete; + Viterbi& operator=(const Viterbi& other) = delete; + void deconvolve(softbit_t *input, uint8_t *output); + + private: + struct v vp; + alignas(16) COMPUTETYPE Branchtab[NUMSTATES / 2 * RATE]; + + // int parityb (uint8_t); + int parity(int x); + void partab_init (void); + // uint8_t Partab [256]; + void init_viterbi(struct v *, int16_t starting_state); + + void update_viterbi_blk_GENERIC( struct v *vp, + COMPUTETYPE *syms, + int16_t nbits); + + void chainback_viterbi( struct v *vp, + uint8_t *data, /* Decoded output data */ + int16_t nbits, /* Number of data bits */ + uint16_t endstate); /*Terminal encoder state */ + + void BFLY( int i, int s, COMPUTETYPE * syms, struct v * vp, decision_t * d); + + uint8_t *data; + COMPUTETYPE *symbols; + int16_t frameBits; +}; + +#endif + diff --git a/src/dabmuxscanner.cpp b/src/dabmuxscanner.cpp index 45c8a5b..b219d15 100644 --- a/src/dabmuxscanner.cpp +++ b/src/dabmuxscanner.cpp @@ -1,392 +1,392 @@ -//--------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//--------------------------------------------------------------------------- - -#include "dabmuxscanner.h" - -#include "stdafx.h" - -#include -#include -#include - -#pragma warning(push, 4) - -// dabmuxscanner::RING_BUFFER_SIZE -// -// Input ring buffer size -size_t const dabmuxscanner::RING_BUFFER_SIZE = (4 MiB); // 1 second @ 2048000 - -// dabmuxscanner::SAMPLE_RATE -// -// Fixed device sample rate required for DAB -uint32_t const dabmuxscanner::SAMPLE_RATE = 2048000; - -// trim (local) -// -// Trims an std::string instance -inline std::string trim(const std::string& str) -{ - auto left = std::find_if_not(str.begin(), str.end(), isspace); - auto right = std::find_if_not(str.rbegin(), str.rend(), isspace); - return (right.base() <= left) ? std::string() : std::string(left, right.base()); -} - -//--------------------------------------------------------------------------- -// dabmuxscanner Constructor (private) -// -// Arguments: -// -// samplerate - Sample rate of the input data -// callback - Callback function to invoke on status change - -dabmuxscanner::dabmuxscanner(uint32_t samplerate, callback const& callback) - : m_callback(callback), m_ringbuffer(RING_BUFFER_SIZE) -{ - assert(samplerate == SAMPLE_RATE); - if (samplerate != SAMPLE_RATE) - throw std::invalid_argument("samplerate"); - - // Construct and initialize the demodulator instance - RadioControllerInterface& controllerinterface = *static_cast(this); - InputInterface& inputinterface = *static_cast(this); - RadioReceiverOptions options = {}; - options.disableCoarseCorrector = true; - m_receiver = make_aligned(controllerinterface, inputinterface, options, 1); - m_receiver->restart(false); -} - -//--------------------------------------------------------------------------- -// dabmuxscanner Destructor - -dabmuxscanner::~dabmuxscanner() -{ - if (m_receiver) - m_receiver->stop(); // Stop receiver - m_receiver.reset(); // Reset receiver instance -} - -//--------------------------------------------------------------------------- -// dabmuxscanner::create (static) -// -// Factory method, creates a new dabmuxscanner instance -// -// Arguments: -// -// samplerate - Sample rate of the input data -// callback - Callback function to invoke on status change - -std::unique_ptr dabmuxscanner::create(uint32_t samplerate, callback const& callback) -{ - return std::unique_ptr(new dabmuxscanner(samplerate, callback)); -} - -//--------------------------------------------------------------------------- -// dabmuxscanner::inputsamples -// -// Pipes input samples into the muliplex scanner -// -// Arguments: -// -// samples - Pointer to the input samples -// length - Length of the input samples, in bytes - -void dabmuxscanner::inputsamples(uint8_t const* samples, size_t length) -{ - bool invokecallback = false; // Flag to invoke the callback - - assert(length <= std::numeric_limits::max()); - m_ringbuffer.putDataIntoBuffer(samples, static_cast(length)); - - // Check for and process any new events - std::unique_lock eventslock(m_eventslock); - if (m_events.empty() == false) - { - - // The threading model is a bit weird here; the callback that queued a new - // event needs to be free to continue execution otherwise the DSP may deadlock - // while we process that event. Combat this by swapping the queue<> with a new - // one, release the lock, then go ahead and process each of the queued events - - event_queue_t events; // Empty event queue<> - m_events.swap(events); // Swap with existing queue<> - eventslock.unlock(); // Release the queue<> lock - - while (!events.empty()) - { - - event_t event = events.front(); // event_t - events.pop(); // Remove from queue<> - - // Sync - // - // Signal synchronization/lock has been achieved - if (event.eventid == eventid_t::Sync) - { - - if (m_muxdata.sync == false) - { - - m_muxdata.sync = true; - invokecallback = true; - } - } - - // LostSync - // - // Signal synchronization/lock has been lost - else if (event.eventid == eventid_t::LostSync) - { - - if (m_muxdata.sync == true) - { - - m_muxdata.sync = false; - invokecallback = true; - } - } - - // ServiceDetected - // - // A new service has been detected - else if (event.eventid == eventid_t::ServiceDetected) - { - - // Iterate over each detected component within the service - for (auto const& component : - m_receiver->getComponents(m_receiver->getService(event.serviceid))) - { - - // We only care about audio components; the presense of an SCId and/or a - // Packet address *appears* to indicate a data-only component - if ((component.SCId == 0) && (component.packetAddress == 0)) - { - - // Check to see if we already have this subchannel in the mux data - auto found = - std::find_if(m_muxdata.subchannels.begin(), m_muxdata.subchannels.end(), - [&](auto const& val) -> bool { - return val.number == static_cast(component.subchannelId); - }); - - // New subchannel detected; the label (name) will come later via SetServiceLabel - if (found == m_muxdata.subchannels.end()) - { - - m_muxdata.subchannels.push_back( - {static_cast(component.subchannelId), std::string()}); - invokecallback = true; - } - } - } - } - - // SetEnsembleLabel - // - // A new ensemble label has been received - else if (event.eventid == eventid_t::SetEnsembleLabel) - { - - std::string label = trim(m_receiver->getEnsembleLabel().utf8_label()); - if (label != m_muxdata.name) - { - - m_muxdata.name = label; - invokecallback = true; - } - } - - // SetServiceLabel - // - // A new service label has been received - else if (event.eventid == eventid_t::SetServiceLabel) - { - - // Iterate over each detected component within the service - Service service = m_receiver->getService(event.serviceid); - for (auto const& component : m_receiver->getComponents(service)) - { - - // Update the label for any subchannels associated with the component - for (auto& it : m_muxdata.subchannels) - { - - if (it.number == static_cast(component.subchannelId)) - { - - std::string label = trim(service.serviceLabel.utf8_label()); - if (label != it.name) - { - - it.name = label; - invokecallback = true; - } - } - } - } - } - } - } - - // If anything about the multiplex has changed, invoke the callback - if (invokecallback) - m_callback(m_muxdata); -} - -//--------------------------------------------------------------------------- -// dabmuxscanner::getSamples (InputInterface) -// -// Reads the specified number of samples from the input device -// -// Arguments: -// -// buffer - Buffer to receive the input samples -// size - Number of samples to read - -int32_t dabmuxscanner::getSamples(DSPCOMPLEX* buffer, int32_t size) -{ - int32_t numsamples = 0; // Number of available samples in the buffer - - // Allocate a temporary buffer to pull the data out of the ring buffer - std::unique_ptr tempbuffer(new uint8_t[size * 2]); - - // Get the data from the ring buffer - numsamples = m_ringbuffer.getDataFromBuffer(tempbuffer.get(), size * 2); - - // Scale the input data from [0,255] to [-1,1] for the demodulator - for (int32_t index = 0; index < numsamples / 2; index++) - { - - buffer[index] = - DSPCOMPLEX((static_cast(tempbuffer[index * 2]) - 128.0f) / 128.0f, // real - (static_cast(tempbuffer[(index * 2) + 1]) - 128.0f) / 128.0f // imaginary - ); - } - - return numsamples / 2; -} - -//--------------------------------------------------------------------------- -// dabmuxscanner::getSamplesToRead (InputInterface) -// -// Gets the number of input samples that are available to read from input -// -// Arguments: -// -// NONE - -int32_t dabmuxscanner::getSamplesToRead(void) -{ - return m_ringbuffer.GetRingBufferReadAvailable() / 2; -} - -//--------------------------------------------------------------------------- -// dabmuxscanner::is_ok (InputInterface) -// -// Determines if the input is still "OK" -// -// Arguments: -// -// NONE - -bool dabmuxscanner::is_ok(void) -{ - return true; -} - -//--------------------------------------------------------------------------- -// dabmuxscanner::restart (InputInterface) -// -// Restarts the input -// -// Arguments: -// -// NONE - -bool dabmuxscanner::restart(void) -{ - return true; -} - -//--------------------------------------------------------------------------- -// dabmuxscanner::onServiceDetected (RadioControllerInterface) -// -// Invoked when a new service was detected -// -// Arguments: -// -// sId - New service identifier - -void dabmuxscanner::onServiceDetected(uint32_t sId) -{ - std::unique_lock lock(m_eventslock); - m_events.emplace(event_t{eventid_t::ServiceDetected, sId}); -} - -//--------------------------------------------------------------------------- -// dabmuxscanner::onSetEnsembleLabel (RadioControllerInterface) -// -// Invoked when the ensemble label has changed -// -// Arguments: -// -// label - New ensemble label - -void dabmuxscanner::onSetEnsembleLabel(DabLabel& /*label*/) -{ - std::unique_lock lock(m_eventslock); - m_events.emplace(event_t{eventid_t::SetEnsembleLabel, 0}); -} - -//--------------------------------------------------------------------------- -// dabmuxscanner::onSetServiceLabel (RadioControllerInterface) -// -// Invoked when a service label has changed -// -// Arguments: -// -// sId - Service identifier -// label - New service label - -void dabmuxscanner::onSetServiceLabel(uint32_t sId, DabLabel& /*label*/) -{ - std::unique_lock lock(m_eventslock); - m_events.emplace(event_t{eventid_t::SetServiceLabel, sId}); -} - -//--------------------------------------------------------------------------- -// dabmuxscanner::onSyncChange (RadioControllerInterface) -// -// Invoked when signal synchronization was acquired or lost -// -// Arguments: -// -// isSync - Synchronization flag - -void dabmuxscanner::onSyncChange(bool isSync) -{ - std::unique_lock lock(m_eventslock); - m_events.emplace(event_t{(isSync) ? eventid_t::Sync : eventid_t::LostSync, 0}); -} - -//--------------------------------------------------------------------------- - -#pragma warning(pop) +//--------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//--------------------------------------------------------------------------- + +#include "dabmuxscanner.h" + +#include "stdafx.h" + +#include +#include +#include + +#pragma warning(push, 4) + +// dabmuxscanner::RING_BUFFER_SIZE +// +// Input ring buffer size +size_t const dabmuxscanner::RING_BUFFER_SIZE = (4 MiB); // 1 second @ 2048000 + +// dabmuxscanner::SAMPLE_RATE +// +// Fixed device sample rate required for DAB +uint32_t const dabmuxscanner::SAMPLE_RATE = 2048000; + +// trim (local) +// +// Trims an std::string instance +inline std::string trim(const std::string& str) +{ + auto left = std::find_if_not(str.begin(), str.end(), isspace); + auto right = std::find_if_not(str.rbegin(), str.rend(), isspace); + return (right.base() <= left) ? std::string() : std::string(left, right.base()); +} + +//--------------------------------------------------------------------------- +// dabmuxscanner Constructor (private) +// +// Arguments: +// +// samplerate - Sample rate of the input data +// callback - Callback function to invoke on status change + +dabmuxscanner::dabmuxscanner(uint32_t samplerate, callback const& callback) + : m_callback(callback), m_ringbuffer(RING_BUFFER_SIZE) +{ + assert(samplerate == SAMPLE_RATE); + if (samplerate != SAMPLE_RATE) + throw std::invalid_argument("samplerate"); + + // Construct and initialize the demodulator instance + RadioControllerInterface& controllerinterface = *static_cast(this); + InputInterface& inputinterface = *static_cast(this); + RadioReceiverOptions options = {}; + options.disableCoarseCorrector = true; + m_receiver = make_aligned(controllerinterface, inputinterface, options, 1); + m_receiver->restart(false); +} + +//--------------------------------------------------------------------------- +// dabmuxscanner Destructor + +dabmuxscanner::~dabmuxscanner() +{ + if (m_receiver) + m_receiver->stop(); // Stop receiver + m_receiver.reset(); // Reset receiver instance +} + +//--------------------------------------------------------------------------- +// dabmuxscanner::create (static) +// +// Factory method, creates a new dabmuxscanner instance +// +// Arguments: +// +// samplerate - Sample rate of the input data +// callback - Callback function to invoke on status change + +std::unique_ptr dabmuxscanner::create(uint32_t samplerate, callback const& callback) +{ + return std::unique_ptr(new dabmuxscanner(samplerate, callback)); +} + +//--------------------------------------------------------------------------- +// dabmuxscanner::inputsamples +// +// Pipes input samples into the muliplex scanner +// +// Arguments: +// +// samples - Pointer to the input samples +// length - Length of the input samples, in bytes + +void dabmuxscanner::inputsamples(uint8_t const* samples, size_t length) +{ + bool invokecallback = false; // Flag to invoke the callback + + assert(length <= std::numeric_limits::max()); + m_ringbuffer.putDataIntoBuffer(samples, static_cast(length)); + + // Check for and process any new events + std::unique_lock eventslock(m_eventslock); + if (m_events.empty() == false) + { + + // The threading model is a bit weird here; the callback that queued a new + // event needs to be free to continue execution otherwise the DSP may deadlock + // while we process that event. Combat this by swapping the queue<> with a new + // one, release the lock, then go ahead and process each of the queued events + + event_queue_t events; // Empty event queue<> + m_events.swap(events); // Swap with existing queue<> + eventslock.unlock(); // Release the queue<> lock + + while (!events.empty()) + { + + event_t event = events.front(); // event_t + events.pop(); // Remove from queue<> + + // Sync + // + // Signal synchronization/lock has been achieved + if (event.eventid == eventid_t::Sync) + { + + if (m_muxdata.sync == false) + { + + m_muxdata.sync = true; + invokecallback = true; + } + } + + // LostSync + // + // Signal synchronization/lock has been lost + else if (event.eventid == eventid_t::LostSync) + { + + if (m_muxdata.sync == true) + { + + m_muxdata.sync = false; + invokecallback = true; + } + } + + // ServiceDetected + // + // A new service has been detected + else if (event.eventid == eventid_t::ServiceDetected) + { + + // Iterate over each detected component within the service + for (auto const& component : + m_receiver->getComponents(m_receiver->getService(event.serviceid))) + { + + // We only care about audio components; the presense of an SCId and/or a + // Packet address *appears* to indicate a data-only component + if ((component.SCId == 0) && (component.packetAddress == 0)) + { + + // Check to see if we already have this subchannel in the mux data + auto found = + std::find_if(m_muxdata.subchannels.begin(), m_muxdata.subchannels.end(), + [&](auto const& val) -> bool { + return val.number == static_cast(component.subchannelId); + }); + + // New subchannel detected; the label (name) will come later via SetServiceLabel + if (found == m_muxdata.subchannels.end()) + { + + m_muxdata.subchannels.push_back( + {static_cast(component.subchannelId), std::string()}); + invokecallback = true; + } + } + } + } + + // SetEnsembleLabel + // + // A new ensemble label has been received + else if (event.eventid == eventid_t::SetEnsembleLabel) + { + + std::string label = trim(m_receiver->getEnsembleLabel().utf8_label()); + if (label != m_muxdata.name) + { + + m_muxdata.name = label; + invokecallback = true; + } + } + + // SetServiceLabel + // + // A new service label has been received + else if (event.eventid == eventid_t::SetServiceLabel) + { + + // Iterate over each detected component within the service + Service service = m_receiver->getService(event.serviceid); + for (auto const& component : m_receiver->getComponents(service)) + { + + // Update the label for any subchannels associated with the component + for (auto& it : m_muxdata.subchannels) + { + + if (it.number == static_cast(component.subchannelId)) + { + + std::string label = trim(service.serviceLabel.utf8_label()); + if (label != it.name) + { + + it.name = label; + invokecallback = true; + } + } + } + } + } + } + } + + // If anything about the multiplex has changed, invoke the callback + if (invokecallback) + m_callback(m_muxdata); +} + +//--------------------------------------------------------------------------- +// dabmuxscanner::getSamples (InputInterface) +// +// Reads the specified number of samples from the input device +// +// Arguments: +// +// buffer - Buffer to receive the input samples +// size - Number of samples to read + +int32_t dabmuxscanner::getSamples(DSPCOMPLEX* buffer, int32_t size) +{ + int32_t numsamples = 0; // Number of available samples in the buffer + + // Allocate a temporary buffer to pull the data out of the ring buffer + std::unique_ptr tempbuffer(new uint8_t[size * 2]); + + // Get the data from the ring buffer + numsamples = m_ringbuffer.getDataFromBuffer(tempbuffer.get(), size * 2); + + // Scale the input data from [0,255] to [-1,1] for the demodulator + for (int32_t index = 0; index < numsamples / 2; index++) + { + + buffer[index] = + DSPCOMPLEX((static_cast(tempbuffer[index * 2]) - 128.0f) / 128.0f, // real + (static_cast(tempbuffer[(index * 2) + 1]) - 128.0f) / 128.0f // imaginary + ); + } + + return numsamples / 2; +} + +//--------------------------------------------------------------------------- +// dabmuxscanner::getSamplesToRead (InputInterface) +// +// Gets the number of input samples that are available to read from input +// +// Arguments: +// +// NONE + +int32_t dabmuxscanner::getSamplesToRead(void) +{ + return m_ringbuffer.GetRingBufferReadAvailable() / 2; +} + +//--------------------------------------------------------------------------- +// dabmuxscanner::is_ok (InputInterface) +// +// Determines if the input is still "OK" +// +// Arguments: +// +// NONE + +bool dabmuxscanner::is_ok(void) +{ + return true; +} + +//--------------------------------------------------------------------------- +// dabmuxscanner::restart (InputInterface) +// +// Restarts the input +// +// Arguments: +// +// NONE + +bool dabmuxscanner::restart(void) +{ + return true; +} + +//--------------------------------------------------------------------------- +// dabmuxscanner::onServiceDetected (RadioControllerInterface) +// +// Invoked when a new service was detected +// +// Arguments: +// +// sId - New service identifier + +void dabmuxscanner::onServiceDetected(uint32_t sId) +{ + std::unique_lock lock(m_eventslock); + m_events.emplace(event_t{eventid_t::ServiceDetected, sId}); +} + +//--------------------------------------------------------------------------- +// dabmuxscanner::onSetEnsembleLabel (RadioControllerInterface) +// +// Invoked when the ensemble label has changed +// +// Arguments: +// +// label - New ensemble label + +void dabmuxscanner::onSetEnsembleLabel(DabLabel& /*label*/) +{ + std::unique_lock lock(m_eventslock); + m_events.emplace(event_t{eventid_t::SetEnsembleLabel, 0}); +} + +//--------------------------------------------------------------------------- +// dabmuxscanner::onSetServiceLabel (RadioControllerInterface) +// +// Invoked when a service label has changed +// +// Arguments: +// +// sId - Service identifier +// label - New service label + +void dabmuxscanner::onSetServiceLabel(uint32_t sId, DabLabel& /*label*/) +{ + std::unique_lock lock(m_eventslock); + m_events.emplace(event_t{eventid_t::SetServiceLabel, sId}); +} + +//--------------------------------------------------------------------------- +// dabmuxscanner::onSyncChange (RadioControllerInterface) +// +// Invoked when signal synchronization was acquired or lost +// +// Arguments: +// +// isSync - Synchronization flag + +void dabmuxscanner::onSyncChange(bool isSync) +{ + std::unique_lock lock(m_eventslock); + m_events.emplace(event_t{(isSync) ? eventid_t::Sync : eventid_t::LostSync, 0}); +} + +//--------------------------------------------------------------------------- + +#pragma warning(pop) diff --git a/src/dabmuxscanner.h b/src/dabmuxscanner.h index 86e8606..2b0605f 100644 --- a/src/dabmuxscanner.h +++ b/src/dabmuxscanner.h @@ -1,190 +1,190 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//----------------------------------------------------------------------------- - -#ifndef __DABMUXSCANNER_H_ -#define __DABMUXSCANNER_H_ -#pragma once - -#include "dabdsp/radio-receiver.h" -#include "dabdsp/ringbuffer.h" -#include "muxscanner.h" - -#include -#include -#include -#include -#include -#include - -#pragma warning(push, 4) - -//--------------------------------------------------------------------------- -// Class dabmuxscanner -// -// Implements the multiplex scanner for DAB - -class dabmuxscanner : public muxscanner, - private InputInterface, - private ProgrammeHandlerInterface, - private RadioControllerInterface -{ -public: - // Destructor - // - virtual ~dabmuxscanner(); - - //----------------------------------------------------------------------- - // Member Functions - - // create (static) - // - // Factory method, creates a new dabmuxscanner instance - static std::unique_ptr create(uint32_t samplerate, callback const& callback); - - // inputsamples - // - // Pipes input samples into the muliplex scanner - void inputsamples(uint8_t const* samples, size_t length) override; - -private: - dabmuxscanner(dabmuxscanner const&) = delete; - dabmuxscanner& operator=(dabmuxscanner const&) = delete; - - // RING_BUFFER_SIZE - // - // Input ring buffer size - static size_t const RING_BUFFER_SIZE; - - // SAMPLE_RATE - // - // Fixed device sample rate required for DAB - static uint32_t const SAMPLE_RATE; - - // Instance Constructor - // - dabmuxscanner(uint32_t samplerate, callback const& callback); - - //----------------------------------------------------------------------- - // Private Type Declarations - - // eventid_t - // - // Defines a worker thread event identifier - enum class eventid_t - { - - LostSync, // Synchronization has been lost - ServiceDetected, // A new service has been detected - SetEnsembleLabel, // The ensemble label has been detected - SetServiceLabel, // A service label has been detected - Sync, // Synchronization has been achieved - }; - - // event_t - // - // Defines a worker thread event - struct event_t - { - - eventid_t eventid; - uint32_t serviceid; - }; - - // event_queue_t - // - // Defines the worker thread event queue - using event_queue_t = std::queue; - - // timepoint_t - // - // Defines a point in time based on steady_clock - using timepoint_t = std::chrono::time_point; - - //----------------------------------------------------------------------- - // InputInterface Implementation - - // getSamples - // - // Reads the specified number of samples from the input device - int32_t getSamples(DSPCOMPLEX* buffer, int32_t size) override; - - // getSamplesToRead - // - // Gets the number of input samples that are available to read from input - int32_t getSamplesToRead(void) override; - - // is_ok - // - // Determines if the input is still "OK" - bool is_ok(void) override; - - // restart - // - // Restarts the input - bool restart(void) override; - - //----------------------------------------------------------------------- - // RadioControllerInterface - - // onServiceDetected - // - // Invoked when a new service was detected - void onServiceDetected(uint32_t sId) override; - - // onSetEnsembleLabel - // - // Invoked when the ensemble label has changed - void onSetEnsembleLabel(DabLabel& label) override; - - // onSetServiceLabel - // - // Invoked when a service label has changed - void onSetServiceLabel(uint32_t sId, DabLabel& label) override; - - // onSyncChange - // - // Invoked when signal synchronization was acquired or lost - void onSyncChange(bool isSync) override; - - //----------------------------------------------------------------------- - // Member Variables - - callback const m_callback; // Callback function - struct multiplex m_muxdata = {}; // Multiplex data - - // DEMODULATOR - // - aligned_ptr m_receiver; // RadioReceiver instance - RingBuffer m_ringbuffer; // I/Q sample ring buffer - - // EVENT QUEUE - // - event_queue_t m_events; // queue<> of worker events - mutable std::mutex m_eventslock; // Synchronization object - std::condition_variable m_eventscv; // Event condition variable -}; - -//----------------------------------------------------------------------------- - -#pragma warning(pop) - -#endif // __DABMUXSCANNER_H_ +//----------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef __DABMUXSCANNER_H_ +#define __DABMUXSCANNER_H_ +#pragma once + +#include "dabdsp/radio-receiver.h" +#include "dabdsp/ringbuffer.h" +#include "muxscanner.h" + +#include +#include +#include +#include +#include +#include + +#pragma warning(push, 4) + +//--------------------------------------------------------------------------- +// Class dabmuxscanner +// +// Implements the multiplex scanner for DAB + +class dabmuxscanner : public muxscanner, + private InputInterface, + private ProgrammeHandlerInterface, + private RadioControllerInterface +{ +public: + // Destructor + // + virtual ~dabmuxscanner(); + + //----------------------------------------------------------------------- + // Member Functions + + // create (static) + // + // Factory method, creates a new dabmuxscanner instance + static std::unique_ptr create(uint32_t samplerate, callback const& callback); + + // inputsamples + // + // Pipes input samples into the muliplex scanner + void inputsamples(uint8_t const* samples, size_t length) override; + +private: + dabmuxscanner(dabmuxscanner const&) = delete; + dabmuxscanner& operator=(dabmuxscanner const&) = delete; + + // RING_BUFFER_SIZE + // + // Input ring buffer size + static size_t const RING_BUFFER_SIZE; + + // SAMPLE_RATE + // + // Fixed device sample rate required for DAB + static uint32_t const SAMPLE_RATE; + + // Instance Constructor + // + dabmuxscanner(uint32_t samplerate, callback const& callback); + + //----------------------------------------------------------------------- + // Private Type Declarations + + // eventid_t + // + // Defines a worker thread event identifier + enum class eventid_t + { + + LostSync, // Synchronization has been lost + ServiceDetected, // A new service has been detected + SetEnsembleLabel, // The ensemble label has been detected + SetServiceLabel, // A service label has been detected + Sync, // Synchronization has been achieved + }; + + // event_t + // + // Defines a worker thread event + struct event_t + { + + eventid_t eventid; + uint32_t serviceid; + }; + + // event_queue_t + // + // Defines the worker thread event queue + using event_queue_t = std::queue; + + // timepoint_t + // + // Defines a point in time based on steady_clock + using timepoint_t = std::chrono::time_point; + + //----------------------------------------------------------------------- + // InputInterface Implementation + + // getSamples + // + // Reads the specified number of samples from the input device + int32_t getSamples(DSPCOMPLEX* buffer, int32_t size) override; + + // getSamplesToRead + // + // Gets the number of input samples that are available to read from input + int32_t getSamplesToRead(void) override; + + // is_ok + // + // Determines if the input is still "OK" + bool is_ok(void) override; + + // restart + // + // Restarts the input + bool restart(void) override; + + //----------------------------------------------------------------------- + // RadioControllerInterface + + // onServiceDetected + // + // Invoked when a new service was detected + void onServiceDetected(uint32_t sId) override; + + // onSetEnsembleLabel + // + // Invoked when the ensemble label has changed + void onSetEnsembleLabel(DabLabel& label) override; + + // onSetServiceLabel + // + // Invoked when a service label has changed + void onSetServiceLabel(uint32_t sId, DabLabel& label) override; + + // onSyncChange + // + // Invoked when signal synchronization was acquired or lost + void onSyncChange(bool isSync) override; + + //----------------------------------------------------------------------- + // Member Variables + + callback const m_callback; // Callback function + struct multiplex m_muxdata = {}; // Multiplex data + + // DEMODULATOR + // + aligned_ptr m_receiver; // RadioReceiver instance + RingBuffer m_ringbuffer; // I/Q sample ring buffer + + // EVENT QUEUE + // + event_queue_t m_events; // queue<> of worker events + mutable std::mutex m_eventslock; // Synchronization object + std::condition_variable m_eventscv; // Event condition variable +}; + +//----------------------------------------------------------------------------- + +#pragma warning(pop) + +#endif // __DABMUXSCANNER_H_ diff --git a/src/dabstream.cpp b/src/dabstream.cpp index 37f222b..c429ed4 100644 --- a/src/dabstream.cpp +++ b/src/dabstream.cpp @@ -1,813 +1,813 @@ -//--------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//--------------------------------------------------------------------------- - -#include "dabstream.h" - -#include "stdafx.h" -#include "string_exception.h" - -#pragma warning(push, 4) - -// dabstream::DEFAULT_AUDIO_RATE -// -// The default audio output sample rate -int const dabstream::DEFAULT_AUDIO_RATE = 48000; - -// dabstream::MAX_PACKET_QUEUE -// -// Maximum number of queued demux packets -size_t const dabstream::MAX_PACKET_QUEUE = 200; // ~5 seconds @ 24ms; 12 seconds @ 60ms - -// dabstream::RING_BUFFER_SIZE -// -// Input ring buffer size -size_t const dabstream::RING_BUFFER_SIZE = (4 MiB); // 1 second @ 2048000 - -// dabstream::SAMPLE_RATE -// -// Fixed device sample rate required for DAB -uint32_t const dabstream::SAMPLE_RATE = 2048000; - -// dabstream::STREAM_ID_AUDIOBASE -// -// Base stream identifier for the audio output stream -int const dabstream::STREAM_ID_AUDIOBASE = 1; - -// dabstream::STREAM_ID_ID3TAG -// -// Stream identifier for the ID3v2 tag output stream -int const dabstream::STREAM_ID_ID3TAG = 0; - -//--------------------------------------------------------------------------- -// dabstream Constructor (private) -// -// Arguments: -// -// device - RTL-SDR device instance -// tunerprops - Tuner device properties -// channelprops - Channel properties -// dabprops - DAB digital signal processor properties -// subchannel - DAB subchannel to decode/stream - -dabstream::dabstream(std::unique_ptr device, - struct tunerprops const& tunerprops, - struct channelprops const& channelprops, - struct dabprops const& dabprops, - uint32_t subchannel) - : m_device(std::move(device)), - m_ringbuffer(RING_BUFFER_SIZE), - m_subchannel((subchannel > 0) ? subchannel : 1), - m_pcmgain(powf(10.0f, dabprops.outputgain / 10.0f)) -{ - // Initialize the RTL-SDR device instance - m_device->set_frequency_correction(tunerprops.freqcorrection + channelprops.freqcorrection); - m_device->set_sample_rate(SAMPLE_RATE); - m_device->set_center_frequency(channelprops.frequency); - - // Adjust the device gain as specified by the channel properties - m_device->set_automatic_gain_control(channelprops.autogain); - if (channelprops.autogain == false) - m_device->set_gain(channelprops.manualgain); - - // Construct and initialize the demodulator instance - RadioControllerInterface& controllerinterface = *static_cast(this); - InputInterface& inputinterface = *static_cast(this); - RadioReceiverOptions options = {}; - options.disableCoarseCorrector = true; - m_receiver = make_aligned(controllerinterface, inputinterface, options, 1); - - // Create the worker thread - scalar_condition started{false}; - m_worker = std::thread(&dabstream::worker, this, std::ref(started)); - started.wait_until_equals(true); -} - -//--------------------------------------------------------------------------- -// dabstream Destructor - -dabstream::~dabstream() -{ - close(); -} - -//--------------------------------------------------------------------------- -// dabstream::canseek -// -// Gets a flag indicating if the stream allows seek operations -// -// Arguments: -// -// NONE - -bool dabstream::canseek(void) const -{ - return false; -} - -//--------------------------------------------------------------------------- -// dabstream::close -// -// Closes the stream -// -// Arguments: -// -// NONE - -void dabstream::close(void) -{ - m_stop = true; // Signal worker thread to stop - if (m_device) - m_device->cancel_async(); // Cancel any async read operations - if (m_worker.joinable()) - m_worker.join(); // Wait for thread - - if (m_receiver) - m_receiver->stop(); // Stop receiver - m_receiver.reset(); // Reset receiver instance - - m_device.reset(); // Release RTL-SDR device -} - -//--------------------------------------------------------------------------- -// dabstream::create (static) -// -// Factory method, creates a new dabstream instance -// -// Arguments: -// -// device - RTL-SDR device instance -// tunerprops - Tunder device properties -// channelprops - Channel properties -// dabprops - DAB digital signal processor properties -// subchannel - DAB subchannel to decode/stream - -std::unique_ptr dabstream::create(std::unique_ptr device, - struct tunerprops const& tunerprops, - struct channelprops const& channelprops, - struct dabprops const& dabprops, - uint32_t subchannel) -{ - return std::unique_ptr( - new dabstream(std::move(device), tunerprops, channelprops, dabprops, subchannel)); -} - -//--------------------------------------------------------------------------- -// dabstream::demuxabort -// -// Aborts the demultiplexer -// -// Arguments: -// -// NONE - -void dabstream::demuxabort(void) -{ -} - -//--------------------------------------------------------------------------- -// dabstream::demuxflush -// -// Flushes the demultiplexer -// -// Arguments: -// -// NONE - -void dabstream::demuxflush(void) -{ -} - -//--------------------------------------------------------------------------- -// dabstream::demuxread -// -// Reads the next packet from the demultiplexer -// -// Arguments: -// -// allocator - DemuxPacket allocation function - -DEMUX_PACKET* dabstream::demuxread(std::function const& allocator) -{ - std::unique_lock lock(m_queuelock); - - // Wait up to 50ms for there to be a packet available for processing - if (!m_queuecv.wait_for(lock, std::chrono::milliseconds(50), - [&]() -> bool - { return ((m_queue.size() > 0) || m_stopped.load() == true); })) - return allocator(0); - - // If the worker thread was stopped, check for and re-throw any exception that occurred, - // otherwise assume it was stopped normally and return an empty demultiplexer packet - if (m_stopped.load() == true) - { - - if (m_worker_exception) - std::rethrow_exception(m_worker_exception); - else - return allocator(0); - } - - // Pop off the topmost object from the queue<> and release the lock - std::unique_ptr packet(std::move(m_queue.front())); - m_queue.pop(); - lock.unlock(); - - // The packet queue should never have a null packet in it - assert(packet); - if (!packet) - return allocator(0); - - // Allocate and initialize the DEMUX_PACKET - DEMUX_PACKET* demuxpacket = allocator(packet->size); - if (demuxpacket != nullptr) - { - - demuxpacket->iStreamId = packet->streamid; - demuxpacket->iSize = packet->size; - demuxpacket->duration = packet->duration; - demuxpacket->dts = packet->dts; - demuxpacket->pts = packet->pts; - if (packet->size > 0) - memcpy(demuxpacket->pData, packet->data.get(), packet->size); - } - - return demuxpacket; -} - -//--------------------------------------------------------------------------- -// dabstream::demuxreset -// -// Resets the demultiplexer -// -// Arguments: -// -// NONE - -void dabstream::demuxreset(void) -{ -} - -//--------------------------------------------------------------------------- -// dabstream::devicename -// -// Gets the device name associated with the stream -// -// Arguments: -// -// NONE - -std::string dabstream::devicename(void) const -{ - return std::string(m_device->get_device_name()); -} - -//--------------------------------------------------------------------------- -// dabstream::enumproperties -// -// Enumerates the stream properties -// -// Arguments: -// -// callback - Callback to invoke for each stream - -void dabstream::enumproperties(std::function const& callback) -{ - // AUDIO STREAM - // - streamprops audio = {}; - audio.codec = "pcm_s16le"; - audio.pid = m_audioid.load(); - audio.channels = 2; - audio.samplerate = m_audiorate.load(); - audio.bitspersample = 16; - callback(audio); -} - -//--------------------------------------------------------------------------- -// dabstream::length -// -// Gets the length of the stream; or -1 if stream is real-time -// -// Arguments: -// -// NONE - -long long dabstream::length(void) const -{ - return -1; -} - -//--------------------------------------------------------------------------- -// dabstream::muxname -// -// Gets the mux name associated with the stream -// -// Arguments: -// -// NONE - -std::string dabstream::muxname(void) const -{ - return ""; -} - -//--------------------------------------------------------------------------- -// dabstream::position -// -// Gets the current position of the stream -// -// Arguments: -// -// NONE - -long long dabstream::position(void) const -{ - return -1; -} - -//--------------------------------------------------------------------------- -// dabstream::read -// -// Reads data from the live stream -// -// Arguments: -// -// buffer - Buffer to receive the live stream data -// count - Size of the destination buffer in bytes - -size_t dabstream::read(uint8_t* /*buffer*/, size_t /*count*/) -{ - return 0; -} - -//--------------------------------------------------------------------------- -// dabstream::realtime -// -// Gets a flag indicating if the stream is real-time -// -// Arguments: -// -// NONE - -bool dabstream::realtime(void) const -{ - return true; -} - -//--------------------------------------------------------------------------- -// dabstream::seek -// -// Sets the stream pointer to a specific position -// -// Arguments: -// -// position - Delta within the stream to seek, relative to whence -// whence - Starting position from which to apply the delta - -long long dabstream::seek(long long /*position*/, int /*whence*/) -{ - return -1; -} - -//--------------------------------------------------------------------------- -// dabstream::servicename -// -// Gets the service name associated with the stream -// -// Arguments: -// -// NONE - -std::string dabstream::servicename(void) const -{ - return ""; // TODO -} - -//--------------------------------------------------------------------------- -// dabstream::signalquality -// -// Gets the signal quality as percentages -// -// Arguments: -// -// NONE - -void dabstream::signalquality(int& quality, int& snr) const -{ - quality = snr = 0; // TODO -} - -//--------------------------------------------------------------------------- -// dabstream::worker (private) -// -// Worker thread procedure used to transfer and process data -// -// Arguments: -// -// started - Condition variable to set when thread has started - -void dabstream::worker(scalar_condition& started) -{ - std::vector servicelist; // vector<> of current services - bool foundsub = false; // Flag indicating the desired subchannel was found - - assert(m_device); - assert(m_receiver); - - // read_callback_func (local) - // - // Asynchronous read callback function for the RTL-SDR device - auto read_callback_func = [&](uint8_t const* buffer, size_t count) -> void - { - // Trigger an InputFailure event if no data has been returned from the device - if (count == 0) - m_streamok.store(false); - - // Copy the input data into the ring buffer - assert(count <= std::numeric_limits::max()); - m_ringbuffer.putDataIntoBuffer(buffer, static_cast(count)); - - // Check for and process any new events - std::unique_lock eventslock(m_eventslock); - if (m_events.empty() == false) - { - - // The threading model is a bit weird here; the callback that queued a new - // event needs to be free to continue execution otherwise the DSP may deadlock - // while we process that event. Combat this by swapping the queue<> with a new - // one, release the lock, then go ahead and process each of the queued events - - event_queue_t events; // Empty event queue<> - m_events.swap(events); // Swap with existing queue<> - eventslock.unlock(); // Release the queue<> lock - - while (!events.empty()) - { - - eventid_t eventid = events.front(); // eventid_t - events.pop(); // Remove from queue<> - - switch (eventid) - { - - // InputFailure - // - // Something has gone wrong with the input stream - case eventid_t::InputFailure: - - throw string_exception("Input Failure"); // TODO: message - break; - - // ServiceDetected - // - // A new service has been detected - case eventid_t::ServiceDetected: - - if (foundsub) - break; // Subchannel has already been found; ignore - - // Determine if the desired subchannel is now present in the decoded services - servicelist = m_receiver->getServiceList(); - for (auto const& service : servicelist) - { - for (auto const& component : m_receiver->getComponents(service)) - { - - if (component.subchannelId == static_cast(m_subchannel)) - { - - // The desired subchannel has been found; begin audio playback - ProgrammeHandlerInterface& phi = *static_cast(this); - m_receiver->playSingleProgramme(phi, {}, service); - - foundsub = true; // Stop processing service events - } - } - } - break; - } - } - } - }; - - // Begin streaming from the device via the receiver and inform the caller that the thread is running - m_receiver->restart(false); - started = true; - - // Continuously read data from the device until cancel_async() has been called - // 40 KiB = ~1/100 of a second of data - try - { - m_device->read_async(read_callback_func, 40 KiB); - } - catch (...) - { - m_worker_exception = std::current_exception(); - } - - m_stopped.store(true); // Worker thread is now stopped - m_queuecv.notify_all(); // Unblock any demux queue waiters -} - -//--------------------------------------------------------------------------- -// dabstream::getSamples (InputInterface) -// -// Reads the specified number of samples from the input device -// -// Arguments: -// -// buffer - Buffer to receive the input samples -// size - Number of samples to read - -int32_t dabstream::getSamples(DSPCOMPLEX* buffer, int32_t size) -{ - int32_t numsamples = 0; // Number of available samples in the buffer - - // Allocate a temporary buffer to pull the data out of the ring buffer - std::unique_ptr tempbuffer(new uint8_t[size * 2]); - - // Get the data from the ring buffer - numsamples = m_ringbuffer.getDataFromBuffer(tempbuffer.get(), size * 2); - - // Scale the input data from [0,255] to [-1,1] for the demodulator - for (int32_t index = 0; index < numsamples / 2; index++) - { - - buffer[index] = - DSPCOMPLEX((static_cast(tempbuffer[index * 2]) - 128.0f) / 128.0f, // real - (static_cast(tempbuffer[(index * 2) + 1]) - 128.0f) / 128.0f // imaginary - ); - } - - return numsamples / 2; -} - -//--------------------------------------------------------------------------- -// dabstream::getSamplesToRead (InputInterface) -// -// Gets the number of input samples that are available to read from input -// -// Arguments: -// -// NONE - -int32_t dabstream::getSamplesToRead(void) -{ - return m_ringbuffer.GetRingBufferReadAvailable() / 2; -} - -//--------------------------------------------------------------------------- -// dabstream::is_ok (InputInterface) -// -// Determines if the input is still "OK" -// -// Arguments: -// -// NONE - -bool dabstream::is_ok(void) -{ - return m_streamok.load(); -} - -//--------------------------------------------------------------------------- -// dabstream::restart (InputInterface) -// -// Restarts the input -// -// Arguments: -// -// NONE - -bool dabstream::restart(void) -{ - assert(m_device); - - m_streamok.store(true); - m_device->begin_stream(); - - return true; -} - -//--------------------------------------------------------------------------- -// dabstream::onNewAudio (ProgrammeHandlerInterface) -// -// Invoked when a new packet of audio data has been decoded -// -// Arguments: -// -// audioData - vector<> of stereo PCM audio data -// sampleRate - Sample rate of the audio data (subject to change) -// mode - Information about the audio encoding - -void dabstream::onNewAudio(std::vector&& audioData, - int sampleRate, - std::string const& /*mode*/) -{ - if (audioData.size() == 0) - return; - - // Allocate the uint8_t buffer to hold the PCM audio data - size_t pcmsize = audioData.size() * sizeof(int16_t); - std::unique_ptr pcm(new uint8_t[pcmsize]); - - // Copy the audio data into the buffer while applying the specified PCM output gain - int16_t* pcmdata = reinterpret_cast(pcm.get()); - for (size_t index = 0; index < audioData.size(); index++) - pcmdata[index] = static_cast(audioData[index] * m_pcmgain); - - std::unique_lock lock(m_queuelock); - - // Detect and handle a change in the audio output sample rate - if (sampleRate != m_audiorate) - { - - m_audioid.fetch_add(1); // Increment the audio stream id - m_audiorate.store(sampleRate); // Change the sample rate - - // Queue a DEMUX_SPECIALID_STREAMCHANGE packet to inform of the stream change - std::unique_ptr packet = std::make_unique(); - packet->streamid = DEMUX_SPECIALID_STREAMCHANGE; - m_queue.emplace(std::move(packet)); - } - - // If the queue size has exceeded the maximum, the packets aren't being - // processed quickly enough by the demux read function - if (m_queue.size() >= MAX_PACKET_QUEUE) - { - - m_queue = demux_queue_t(); // Replace the queue<> - - // Queue a DEMUX_SPECIALID_STREAMCHANGE packet into the new queue - std::unique_ptr packet = std::make_unique(); - packet->streamid = DEMUX_SPECIALID_STREAMCHANGE; - m_queue.emplace(std::move(packet)); - - m_dts = STREAM_TIME_BASE; // Reset DTS back to base time - } - - // Generate and queue the demux audio packet - std::unique_ptr packet = std::make_unique(); - packet->streamid = m_audioid.load(); - packet->size = static_cast(pcmsize); - packet->duration = (audioData.size() / 2.0 / static_cast(sampleRate)) * STREAM_TIME_BASE; - packet->dts = packet->pts = m_dts; - packet->data = std::move(pcm); - - m_dts += packet->duration; - - m_queue.emplace(std::move(packet)); - m_queuecv.notify_all(); -} - -//--------------------------------------------------------------------------- -// dabstream::onNewDynamicLabel (ProgrammeHandlerInterface) -// -// Invoked when a new dynamic label has been decoded -// -// Arguments: -// -// label - The new dynamic label (UTF-8) - -void dabstream::onNewDynamicLabel(std::string const& /*label*/) -{ - // TODO -} - -//--------------------------------------------------------------------------- -// dabstream::onMOT (ProgrammeHandlerInterface) -// -// Invoked when a new dynamic label has been decoded -// -// Arguments: -// -// mot_file - The new slide data - -void dabstream::onMOT(mot_file_t const& /*mot_file*/) -{ - // Content sub type 0x01 = JPEG - // Content sub type 0x03 = PNG - - // - // TODO: I need a sample that contains a MOT frame - // -} - -//--------------------------------------------------------------------------- -// dabstream::onFrequencyCorrectorChange (RadioControllerInterface) -// -// Invoked when the frequency correction has been changed -// -// Arguments: -// -// fine - Fine frequency correction value -// coarse - Coarse frequency correction value - -void dabstream::onFrequencyCorrectorChange(int /*fine*/, int /*coarse*/) -{ - // TODO - This can be applied to the device real-time? -} - -//--------------------------------------------------------------------------- -// dabstream::onInputFailure (RadioControllerInterface) -// -// Invoked when the receiver has shut down to an input failure -// -// Arguments: -// -// NONE - -void dabstream::onInputFailure(void) -{ - std::unique_lock lock(m_eventslock); - m_events.emplace(eventid_t::InputFailure); -} - -//--------------------------------------------------------------------------- -// dabstream::onServiceDetected (RadioControllerInterface) -// -// Invoked when a new service was detected -// -// Arguments: -// -// sId - New service identifier - -void dabstream::onServiceDetected(uint32_t /*sId*/) -{ - std::unique_lock lock(m_eventslock); - m_events.emplace(eventid_t::ServiceDetected); -} - -//--------------------------------------------------------------------------- -// dabstream::onSetEnsembleLabel (RadioControllerInterface) -// -// Invoked when the ensemble label has changed -// -// Arguments: -// -// label - New ensemble label - -void dabstream::onSetEnsembleLabel(DabLabel& /*label*/) -{ - // - // TODO: This can probably be used to automatically generate - // a channel group - // -} - -//--------------------------------------------------------------------------- -// dabstream::onSNR (RadioControllerInterface) -// -// Invoked when the Signal-to-Nosie Radio has been calculated -// -// Arguments: -// -// snr - Signal-to-Noise Ratio, in dB - -void dabstream::onSNR(float /*snr*/) -{ - // - // TODO: Figure out what an acceptable SNR is for DAB and provide - // the necessary event to change the signal quality metric - // -} - -//--------------------------------------------------------------------------- -// dabstream::onSyncChange (RadioControllerInterface) -// -// Invoked when signal synchronization was acquired or lost -// -// Arguments: -// -// isSync - Synchronization flag - -void dabstream::onSyncChange(bool /*isSync*/) -{ - // - // TODO: This might need to STREAMCHANGE, clear the demux queue, - // silence the audio, and maybe throw up a banner to the user - // -} - -//--------------------------------------------------------------------------- - -#pragma warning(pop) +//--------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//--------------------------------------------------------------------------- + +#include "dabstream.h" + +#include "stdafx.h" +#include "string_exception.h" + +#pragma warning(push, 4) + +// dabstream::DEFAULT_AUDIO_RATE +// +// The default audio output sample rate +int const dabstream::DEFAULT_AUDIO_RATE = 48000; + +// dabstream::MAX_PACKET_QUEUE +// +// Maximum number of queued demux packets +size_t const dabstream::MAX_PACKET_QUEUE = 200; // ~5 seconds @ 24ms; 12 seconds @ 60ms + +// dabstream::RING_BUFFER_SIZE +// +// Input ring buffer size +size_t const dabstream::RING_BUFFER_SIZE = (4 MiB); // 1 second @ 2048000 + +// dabstream::SAMPLE_RATE +// +// Fixed device sample rate required for DAB +uint32_t const dabstream::SAMPLE_RATE = 2048000; + +// dabstream::STREAM_ID_AUDIOBASE +// +// Base stream identifier for the audio output stream +int const dabstream::STREAM_ID_AUDIOBASE = 1; + +// dabstream::STREAM_ID_ID3TAG +// +// Stream identifier for the ID3v2 tag output stream +int const dabstream::STREAM_ID_ID3TAG = 0; + +//--------------------------------------------------------------------------- +// dabstream Constructor (private) +// +// Arguments: +// +// device - RTL-SDR device instance +// tunerprops - Tuner device properties +// channelprops - Channel properties +// dabprops - DAB digital signal processor properties +// subchannel - DAB subchannel to decode/stream + +dabstream::dabstream(std::unique_ptr device, + struct tunerprops const& tunerprops, + struct channelprops const& channelprops, + struct dabprops const& dabprops, + uint32_t subchannel) + : m_device(std::move(device)), + m_ringbuffer(RING_BUFFER_SIZE), + m_subchannel((subchannel > 0) ? subchannel : 1), + m_pcmgain(powf(10.0f, dabprops.outputgain / 10.0f)) +{ + // Initialize the RTL-SDR device instance + m_device->set_frequency_correction(tunerprops.freqcorrection + channelprops.freqcorrection); + m_device->set_sample_rate(SAMPLE_RATE); + m_device->set_center_frequency(channelprops.frequency); + + // Adjust the device gain as specified by the channel properties + m_device->set_automatic_gain_control(channelprops.autogain); + if (channelprops.autogain == false) + m_device->set_gain(channelprops.manualgain); + + // Construct and initialize the demodulator instance + RadioControllerInterface& controllerinterface = *static_cast(this); + InputInterface& inputinterface = *static_cast(this); + RadioReceiverOptions options = {}; + options.disableCoarseCorrector = true; + m_receiver = make_aligned(controllerinterface, inputinterface, options, 1); + + // Create the worker thread + scalar_condition started{false}; + m_worker = std::thread(&dabstream::worker, this, std::ref(started)); + started.wait_until_equals(true); +} + +//--------------------------------------------------------------------------- +// dabstream Destructor + +dabstream::~dabstream() +{ + close(); +} + +//--------------------------------------------------------------------------- +// dabstream::canseek +// +// Gets a flag indicating if the stream allows seek operations +// +// Arguments: +// +// NONE + +bool dabstream::canseek(void) const +{ + return false; +} + +//--------------------------------------------------------------------------- +// dabstream::close +// +// Closes the stream +// +// Arguments: +// +// NONE + +void dabstream::close(void) +{ + m_stop = true; // Signal worker thread to stop + if (m_device) + m_device->cancel_async(); // Cancel any async read operations + if (m_worker.joinable()) + m_worker.join(); // Wait for thread + + if (m_receiver) + m_receiver->stop(); // Stop receiver + m_receiver.reset(); // Reset receiver instance + + m_device.reset(); // Release RTL-SDR device +} + +//--------------------------------------------------------------------------- +// dabstream::create (static) +// +// Factory method, creates a new dabstream instance +// +// Arguments: +// +// device - RTL-SDR device instance +// tunerprops - Tunder device properties +// channelprops - Channel properties +// dabprops - DAB digital signal processor properties +// subchannel - DAB subchannel to decode/stream + +std::unique_ptr dabstream::create(std::unique_ptr device, + struct tunerprops const& tunerprops, + struct channelprops const& channelprops, + struct dabprops const& dabprops, + uint32_t subchannel) +{ + return std::unique_ptr( + new dabstream(std::move(device), tunerprops, channelprops, dabprops, subchannel)); +} + +//--------------------------------------------------------------------------- +// dabstream::demuxabort +// +// Aborts the demultiplexer +// +// Arguments: +// +// NONE + +void dabstream::demuxabort(void) +{ +} + +//--------------------------------------------------------------------------- +// dabstream::demuxflush +// +// Flushes the demultiplexer +// +// Arguments: +// +// NONE + +void dabstream::demuxflush(void) +{ +} + +//--------------------------------------------------------------------------- +// dabstream::demuxread +// +// Reads the next packet from the demultiplexer +// +// Arguments: +// +// allocator - DemuxPacket allocation function + +DEMUX_PACKET* dabstream::demuxread(std::function const& allocator) +{ + std::unique_lock lock(m_queuelock); + + // Wait up to 50ms for there to be a packet available for processing + if (!m_queuecv.wait_for(lock, std::chrono::milliseconds(50), + [&]() -> bool + { return ((m_queue.size() > 0) || m_stopped.load() == true); })) + return allocator(0); + + // If the worker thread was stopped, check for and re-throw any exception that occurred, + // otherwise assume it was stopped normally and return an empty demultiplexer packet + if (m_stopped.load() == true) + { + + if (m_worker_exception) + std::rethrow_exception(m_worker_exception); + else + return allocator(0); + } + + // Pop off the topmost object from the queue<> and release the lock + std::unique_ptr packet(std::move(m_queue.front())); + m_queue.pop(); + lock.unlock(); + + // The packet queue should never have a null packet in it + assert(packet); + if (!packet) + return allocator(0); + + // Allocate and initialize the DEMUX_PACKET + DEMUX_PACKET* demuxpacket = allocator(packet->size); + if (demuxpacket != nullptr) + { + + demuxpacket->iStreamId = packet->streamid; + demuxpacket->iSize = packet->size; + demuxpacket->duration = packet->duration; + demuxpacket->dts = packet->dts; + demuxpacket->pts = packet->pts; + if (packet->size > 0) + memcpy(demuxpacket->pData, packet->data.get(), packet->size); + } + + return demuxpacket; +} + +//--------------------------------------------------------------------------- +// dabstream::demuxreset +// +// Resets the demultiplexer +// +// Arguments: +// +// NONE + +void dabstream::demuxreset(void) +{ +} + +//--------------------------------------------------------------------------- +// dabstream::devicename +// +// Gets the device name associated with the stream +// +// Arguments: +// +// NONE + +std::string dabstream::devicename(void) const +{ + return std::string(m_device->get_device_name()); +} + +//--------------------------------------------------------------------------- +// dabstream::enumproperties +// +// Enumerates the stream properties +// +// Arguments: +// +// callback - Callback to invoke for each stream + +void dabstream::enumproperties(std::function const& callback) +{ + // AUDIO STREAM + // + streamprops audio = {}; + audio.codec = "pcm_s16le"; + audio.pid = m_audioid.load(); + audio.channels = 2; + audio.samplerate = m_audiorate.load(); + audio.bitspersample = 16; + callback(audio); +} + +//--------------------------------------------------------------------------- +// dabstream::length +// +// Gets the length of the stream; or -1 if stream is real-time +// +// Arguments: +// +// NONE + +long long dabstream::length(void) const +{ + return -1; +} + +//--------------------------------------------------------------------------- +// dabstream::muxname +// +// Gets the mux name associated with the stream +// +// Arguments: +// +// NONE + +std::string dabstream::muxname(void) const +{ + return ""; +} + +//--------------------------------------------------------------------------- +// dabstream::position +// +// Gets the current position of the stream +// +// Arguments: +// +// NONE + +long long dabstream::position(void) const +{ + return -1; +} + +//--------------------------------------------------------------------------- +// dabstream::read +// +// Reads data from the live stream +// +// Arguments: +// +// buffer - Buffer to receive the live stream data +// count - Size of the destination buffer in bytes + +size_t dabstream::read(uint8_t* /*buffer*/, size_t /*count*/) +{ + return 0; +} + +//--------------------------------------------------------------------------- +// dabstream::realtime +// +// Gets a flag indicating if the stream is real-time +// +// Arguments: +// +// NONE + +bool dabstream::realtime(void) const +{ + return true; +} + +//--------------------------------------------------------------------------- +// dabstream::seek +// +// Sets the stream pointer to a specific position +// +// Arguments: +// +// position - Delta within the stream to seek, relative to whence +// whence - Starting position from which to apply the delta + +long long dabstream::seek(long long /*position*/, int /*whence*/) +{ + return -1; +} + +//--------------------------------------------------------------------------- +// dabstream::servicename +// +// Gets the service name associated with the stream +// +// Arguments: +// +// NONE + +std::string dabstream::servicename(void) const +{ + return ""; // TODO +} + +//--------------------------------------------------------------------------- +// dabstream::signalquality +// +// Gets the signal quality as percentages +// +// Arguments: +// +// NONE + +void dabstream::signalquality(int& quality, int& snr) const +{ + quality = snr = 0; // TODO +} + +//--------------------------------------------------------------------------- +// dabstream::worker (private) +// +// Worker thread procedure used to transfer and process data +// +// Arguments: +// +// started - Condition variable to set when thread has started + +void dabstream::worker(scalar_condition& started) +{ + std::vector servicelist; // vector<> of current services + bool foundsub = false; // Flag indicating the desired subchannel was found + + assert(m_device); + assert(m_receiver); + + // read_callback_func (local) + // + // Asynchronous read callback function for the RTL-SDR device + auto read_callback_func = [&](uint8_t const* buffer, size_t count) -> void + { + // Trigger an InputFailure event if no data has been returned from the device + if (count == 0) + m_streamok.store(false); + + // Copy the input data into the ring buffer + assert(count <= std::numeric_limits::max()); + m_ringbuffer.putDataIntoBuffer(buffer, static_cast(count)); + + // Check for and process any new events + std::unique_lock eventslock(m_eventslock); + if (m_events.empty() == false) + { + + // The threading model is a bit weird here; the callback that queued a new + // event needs to be free to continue execution otherwise the DSP may deadlock + // while we process that event. Combat this by swapping the queue<> with a new + // one, release the lock, then go ahead and process each of the queued events + + event_queue_t events; // Empty event queue<> + m_events.swap(events); // Swap with existing queue<> + eventslock.unlock(); // Release the queue<> lock + + while (!events.empty()) + { + + eventid_t eventid = events.front(); // eventid_t + events.pop(); // Remove from queue<> + + switch (eventid) + { + + // InputFailure + // + // Something has gone wrong with the input stream + case eventid_t::InputFailure: + + throw string_exception("Input Failure"); // TODO: message + break; + + // ServiceDetected + // + // A new service has been detected + case eventid_t::ServiceDetected: + + if (foundsub) + break; // Subchannel has already been found; ignore + + // Determine if the desired subchannel is now present in the decoded services + servicelist = m_receiver->getServiceList(); + for (auto const& service : servicelist) + { + for (auto const& component : m_receiver->getComponents(service)) + { + + if (component.subchannelId == static_cast(m_subchannel)) + { + + // The desired subchannel has been found; begin audio playback + ProgrammeHandlerInterface& phi = *static_cast(this); + m_receiver->playSingleProgramme(phi, {}, service); + + foundsub = true; // Stop processing service events + } + } + } + break; + } + } + } + }; + + // Begin streaming from the device via the receiver and inform the caller that the thread is running + m_receiver->restart(false); + started = true; + + // Continuously read data from the device until cancel_async() has been called + // 40 KiB = ~1/100 of a second of data + try + { + m_device->read_async(read_callback_func, 40 KiB); + } + catch (...) + { + m_worker_exception = std::current_exception(); + } + + m_stopped.store(true); // Worker thread is now stopped + m_queuecv.notify_all(); // Unblock any demux queue waiters +} + +//--------------------------------------------------------------------------- +// dabstream::getSamples (InputInterface) +// +// Reads the specified number of samples from the input device +// +// Arguments: +// +// buffer - Buffer to receive the input samples +// size - Number of samples to read + +int32_t dabstream::getSamples(DSPCOMPLEX* buffer, int32_t size) +{ + int32_t numsamples = 0; // Number of available samples in the buffer + + // Allocate a temporary buffer to pull the data out of the ring buffer + std::unique_ptr tempbuffer(new uint8_t[size * 2]); + + // Get the data from the ring buffer + numsamples = m_ringbuffer.getDataFromBuffer(tempbuffer.get(), size * 2); + + // Scale the input data from [0,255] to [-1,1] for the demodulator + for (int32_t index = 0; index < numsamples / 2; index++) + { + + buffer[index] = + DSPCOMPLEX((static_cast(tempbuffer[index * 2]) - 128.0f) / 128.0f, // real + (static_cast(tempbuffer[(index * 2) + 1]) - 128.0f) / 128.0f // imaginary + ); + } + + return numsamples / 2; +} + +//--------------------------------------------------------------------------- +// dabstream::getSamplesToRead (InputInterface) +// +// Gets the number of input samples that are available to read from input +// +// Arguments: +// +// NONE + +int32_t dabstream::getSamplesToRead(void) +{ + return m_ringbuffer.GetRingBufferReadAvailable() / 2; +} + +//--------------------------------------------------------------------------- +// dabstream::is_ok (InputInterface) +// +// Determines if the input is still "OK" +// +// Arguments: +// +// NONE + +bool dabstream::is_ok(void) +{ + return m_streamok.load(); +} + +//--------------------------------------------------------------------------- +// dabstream::restart (InputInterface) +// +// Restarts the input +// +// Arguments: +// +// NONE + +bool dabstream::restart(void) +{ + assert(m_device); + + m_streamok.store(true); + m_device->begin_stream(); + + return true; +} + +//--------------------------------------------------------------------------- +// dabstream::onNewAudio (ProgrammeHandlerInterface) +// +// Invoked when a new packet of audio data has been decoded +// +// Arguments: +// +// audioData - vector<> of stereo PCM audio data +// sampleRate - Sample rate of the audio data (subject to change) +// mode - Information about the audio encoding + +void dabstream::onNewAudio(std::vector&& audioData, + int sampleRate, + std::string const& /*mode*/) +{ + if (audioData.size() == 0) + return; + + // Allocate the uint8_t buffer to hold the PCM audio data + size_t pcmsize = audioData.size() * sizeof(int16_t); + std::unique_ptr pcm(new uint8_t[pcmsize]); + + // Copy the audio data into the buffer while applying the specified PCM output gain + int16_t* pcmdata = reinterpret_cast(pcm.get()); + for (size_t index = 0; index < audioData.size(); index++) + pcmdata[index] = static_cast(audioData[index] * m_pcmgain); + + std::unique_lock lock(m_queuelock); + + // Detect and handle a change in the audio output sample rate + if (sampleRate != m_audiorate) + { + + m_audioid.fetch_add(1); // Increment the audio stream id + m_audiorate.store(sampleRate); // Change the sample rate + + // Queue a DEMUX_SPECIALID_STREAMCHANGE packet to inform of the stream change + std::unique_ptr packet = std::make_unique(); + packet->streamid = DEMUX_SPECIALID_STREAMCHANGE; + m_queue.emplace(std::move(packet)); + } + + // If the queue size has exceeded the maximum, the packets aren't being + // processed quickly enough by the demux read function + if (m_queue.size() >= MAX_PACKET_QUEUE) + { + + m_queue = demux_queue_t(); // Replace the queue<> + + // Queue a DEMUX_SPECIALID_STREAMCHANGE packet into the new queue + std::unique_ptr packet = std::make_unique(); + packet->streamid = DEMUX_SPECIALID_STREAMCHANGE; + m_queue.emplace(std::move(packet)); + + m_dts = STREAM_TIME_BASE; // Reset DTS back to base time + } + + // Generate and queue the demux audio packet + std::unique_ptr packet = std::make_unique(); + packet->streamid = m_audioid.load(); + packet->size = static_cast(pcmsize); + packet->duration = (audioData.size() / 2.0 / static_cast(sampleRate)) * STREAM_TIME_BASE; + packet->dts = packet->pts = m_dts; + packet->data = std::move(pcm); + + m_dts += packet->duration; + + m_queue.emplace(std::move(packet)); + m_queuecv.notify_all(); +} + +//--------------------------------------------------------------------------- +// dabstream::onNewDynamicLabel (ProgrammeHandlerInterface) +// +// Invoked when a new dynamic label has been decoded +// +// Arguments: +// +// label - The new dynamic label (UTF-8) + +void dabstream::onNewDynamicLabel(std::string const& /*label*/) +{ + // TODO +} + +//--------------------------------------------------------------------------- +// dabstream::onMOT (ProgrammeHandlerInterface) +// +// Invoked when a new dynamic label has been decoded +// +// Arguments: +// +// mot_file - The new slide data + +void dabstream::onMOT(mot_file_t const& /*mot_file*/) +{ + // Content sub type 0x01 = JPEG + // Content sub type 0x03 = PNG + + // + // TODO: I need a sample that contains a MOT frame + // +} + +//--------------------------------------------------------------------------- +// dabstream::onFrequencyCorrectorChange (RadioControllerInterface) +// +// Invoked when the frequency correction has been changed +// +// Arguments: +// +// fine - Fine frequency correction value +// coarse - Coarse frequency correction value + +void dabstream::onFrequencyCorrectorChange(int /*fine*/, int /*coarse*/) +{ + // TODO - This can be applied to the device real-time? +} + +//--------------------------------------------------------------------------- +// dabstream::onInputFailure (RadioControllerInterface) +// +// Invoked when the receiver has shut down to an input failure +// +// Arguments: +// +// NONE + +void dabstream::onInputFailure(void) +{ + std::unique_lock lock(m_eventslock); + m_events.emplace(eventid_t::InputFailure); +} + +//--------------------------------------------------------------------------- +// dabstream::onServiceDetected (RadioControllerInterface) +// +// Invoked when a new service was detected +// +// Arguments: +// +// sId - New service identifier + +void dabstream::onServiceDetected(uint32_t /*sId*/) +{ + std::unique_lock lock(m_eventslock); + m_events.emplace(eventid_t::ServiceDetected); +} + +//--------------------------------------------------------------------------- +// dabstream::onSetEnsembleLabel (RadioControllerInterface) +// +// Invoked when the ensemble label has changed +// +// Arguments: +// +// label - New ensemble label + +void dabstream::onSetEnsembleLabel(DabLabel& /*label*/) +{ + // + // TODO: This can probably be used to automatically generate + // a channel group + // +} + +//--------------------------------------------------------------------------- +// dabstream::onSNR (RadioControllerInterface) +// +// Invoked when the Signal-to-Nosie Radio has been calculated +// +// Arguments: +// +// snr - Signal-to-Noise Ratio, in dB + +void dabstream::onSNR(float /*snr*/) +{ + // + // TODO: Figure out what an acceptable SNR is for DAB and provide + // the necessary event to change the signal quality metric + // +} + +//--------------------------------------------------------------------------- +// dabstream::onSyncChange (RadioControllerInterface) +// +// Invoked when signal synchronization was acquired or lost +// +// Arguments: +// +// isSync - Synchronization flag + +void dabstream::onSyncChange(bool /*isSync*/) +{ + // + // TODO: This might need to STREAMCHANGE, clear the demux queue, + // silence the audio, and maybe throw up a banner to the user + // +} + +//--------------------------------------------------------------------------- + +#pragma warning(pop) diff --git a/src/dabstream.h b/src/dabstream.h index fdcbe08..d2dd83f 100644 --- a/src/dabstream.h +++ b/src/dabstream.h @@ -1,350 +1,350 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//----------------------------------------------------------------------------- - -#ifndef __DABSTREAM_H_ -#define __DABSTREAM_H_ -#pragma once - -#include "dabdsp/radio-receiver.h" -#include "dabdsp/ringbuffer.h" -#include "props.h" -#include "pvrstream.h" -#include "rtldevice.h" -#include "scalar_condition.h" - -#include -#include -#include -#include -#include -#include - -#pragma warning(push, 4) - -//--------------------------------------------------------------------------- -// Class dabstream -// -// Implements a DAB stream - -class dabstream : public pvrstream, - private InputInterface, - private ProgrammeHandlerInterface, - private RadioControllerInterface -{ -public: - // Destructor - // - virtual ~dabstream(); - - //----------------------------------------------------------------------- - // Member Functions - - // canseek - // - // Flag indicating if the stream allows seek operations - bool canseek(void) const override; - - // close - // - // Closes the stream - void close(void) override; - - // create (static) - // - // Factory method, creates a new dab instance - static std::unique_ptr create(std::unique_ptr device, - struct tunerprops const& tunerprops, - struct channelprops const& channelprops, - struct dabprops const& dabprops, - uint32_t subchannel); - - // demuxabort - // - // Aborts the demultiplexer - void demuxabort(void) override; - - // demuxflush - // - // Flushes the demultiplexer - void demuxflush(void) override; - - // demuxread - // - // Reads the next packet from the demultiplexer - DEMUX_PACKET* demuxread(std::function const& allocator) override; - - // demuxreset - // - // Resets the demultiplexer - void demuxreset(void) override; - - // devicename - // - // Gets the device name associated with the stream - std::string devicename(void) const override; - - // enumproperties - // - // Enumerates the stream properties - void enumproperties( - std::function const& callback) override; - - // length - // - // Gets the length of the stream - long long length(void) const override; - - // muxname - // - // Gets the mux name associated with the stream - std::string muxname(void) const override; - - // position - // - // Gets the current position of the stream - long long position(void) const override; - - // read - // - // Reads available data from the stream - size_t read(uint8_t* buffer, size_t count) override; - - // realtime - // - // Gets a flag indicating if the stream is real-time - bool realtime(void) const override; - - // seek - // - // Sets the stream pointer to a specific position - long long seek(long long position, int whence) override; - - // servicename - // - // Gets the service name associated with the stream - std::string servicename(void) const override; - - // signalquality - // - // Gets the signal quality as percentages - void signalquality(int& quality, int& snr) const override; - -private: - dabstream(dabstream const&) = delete; - dabstream& operator=(dabstream const&) = delete; - - // DEFAULT_AUDIO_RATE - // - // The default audio output sample rate - static int const DEFAULT_AUDIO_RATE; - - // MAX_PACKET_QUEUE - // - // Maximum number of queued demux packets - static size_t const MAX_PACKET_QUEUE; - - // RING_BUFFER_SIZE - // - // Input ring buffer size - static size_t const RING_BUFFER_SIZE; - - // SAMPLE_RATE - // - // Fixed device sample rate required for DAB - static uint32_t const SAMPLE_RATE; - - // STREAM_ID_AUDIOBASE - // - // Base stream identifier for the audio output stream - static int const STREAM_ID_AUDIOBASE; - - // STREAM_ID_ID3TAG - // - // Stream identifier for the ID3v2 tag output stream - static int const STREAM_ID_ID3TAG; - - // Instance Constructor - // - dabstream(std::unique_ptr device, - struct tunerprops const& tunerprops, - struct channelprops const& channelprops, - struct dabprops const& dabprops, - uint32_t subchannel); - - //----------------------------------------------------------------------- - // Private Type Declarations - - // demux_packet_t - // - // Defines the conents of a queued demux packet - struct demux_packet_t - { - - int streamid = 0; - int size = 0; - double duration = 0; - double dts = 0; - double pts = 0; - std::unique_ptr data; - }; - - // demux_queue_t - // - // Defines the type of the demux queue - using demux_queue_t = std::queue>; - - // eventid_t - // - // Defines a worker thread event identifier - enum class eventid_t - { - - InputFailure, // An input failure has occurred - ServiceDetected, // A new service has been detected - }; - - // event_queue_t - // - // Defines the type of the worker thread event queue - using event_queue_t = std::queue; - - //----------------------------------------------------------------------- - // Private Member Functions - - // worker - // - // Worker thread procedure used to transfer and process data - void worker(scalar_condition& started); - - //----------------------------------------------------------------------- - // InputInterface Implementation - - // getSamples - // - // Reads the specified number of samples from the input device - int32_t getSamples(DSPCOMPLEX* buffer, int32_t size) override; - - // getSamplesToRead - // - // Gets the number of input samples that are available to read from input - int32_t getSamplesToRead(void) override; - - // is_ok - // - // Determines if the input is still "OK" - bool is_ok(void) override; - - // restart - // - // Restarts the input - bool restart(void) override; - - //----------------------------------------------------------------------- - // ProgrammeHandlerInterface - - // onNewAudio - // - // Invoked when a new packet of audio data has been decoded - void onNewAudio(std::vector&& audioData, - int sampleRate, - const std::string& mode) override; - - // onNewDynamicLabel - // - // Invoked when a new dynamic label has been decoded - void onNewDynamicLabel(const std::string& label) override; - - // onMOT - // - // Invoked when a new slide has been decoded - void onMOT(const mot_file_t& mot_file) override; - - //----------------------------------------------------------------------- - // RadioControllerInterface - - // onFrequencyCorrectorChange - // - // Invoked when the frequency correction has been changed - void onFrequencyCorrectorChange(int fine, int coarse) override; - - // onInputFailure - // - // Invoked when the receiver has shut down to an input failure - void onInputFailure(void) override; - - // onServiceDetected - // - // Invoked when a new service was detected - void onServiceDetected(uint32_t sId) override; - - // onSetEnsembleLabel - // - // Invoked when the ensemble label has changed - void onSetEnsembleLabel(DabLabel& label) override; - - // onSNR - // - // Invoked when the Signal-to-Nosie Radio has been calculated - void onSNR(float snr) override; - - // onSyncChange - // - // Invoked when signal synchronization was acquired or lost - void onSyncChange(bool isSync) override; - - //----------------------------------------------------------------------- - // Member Variables - - std::unique_ptr m_device; // RTL-SDR device instance - aligned_ptr m_receiver; // RadioReceiver instance - RingBuffer m_ringbuffer; // I/Q sample ring buffer - - // STREAM CONTROL - // - uint32_t const m_subchannel; // Ensemble subchannel number - float const m_pcmgain; // Output gain - std::atomic m_streamok{true}; // "OK" flag for the stream - double m_dts{STREAM_TIME_BASE}; // Current decode time stamp - std::atomic m_audioid{STREAM_ID_AUDIOBASE}; // Current audio stream id - std::atomic m_audiorate{DEFAULT_AUDIO_RATE}; // Current audio output rate - - // DEMUX QUEUE - // - demux_queue_t m_queue; // queue<> of demux objects - mutable std::mutex m_queuelock; // Synchronization object - std::condition_variable m_queuecv; // Event condition variable - - // WORKER THREAD - // - std::thread m_worker; // Data transfer thread - std::exception_ptr m_worker_exception; // Exception on worker thread - scalar_condition m_stop{false}; // Condition to stop data transfer - std::atomic m_stopped{false}; // Data transfer stopped flag - event_queue_t m_events; // queue<> of worker events - mutable std::mutex m_eventslock; // Synchronization object -}; - -//----------------------------------------------------------------------------- - -#pragma warning(pop) - -#endif // __HDSTREAM_H_ +//----------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef __DABSTREAM_H_ +#define __DABSTREAM_H_ +#pragma once + +#include "dabdsp/radio-receiver.h" +#include "dabdsp/ringbuffer.h" +#include "props.h" +#include "pvrstream.h" +#include "rtldevice.h" +#include "scalar_condition.h" + +#include +#include +#include +#include +#include +#include + +#pragma warning(push, 4) + +//--------------------------------------------------------------------------- +// Class dabstream +// +// Implements a DAB stream + +class dabstream : public pvrstream, + private InputInterface, + private ProgrammeHandlerInterface, + private RadioControllerInterface +{ +public: + // Destructor + // + virtual ~dabstream(); + + //----------------------------------------------------------------------- + // Member Functions + + // canseek + // + // Flag indicating if the stream allows seek operations + bool canseek(void) const override; + + // close + // + // Closes the stream + void close(void) override; + + // create (static) + // + // Factory method, creates a new dab instance + static std::unique_ptr create(std::unique_ptr device, + struct tunerprops const& tunerprops, + struct channelprops const& channelprops, + struct dabprops const& dabprops, + uint32_t subchannel); + + // demuxabort + // + // Aborts the demultiplexer + void demuxabort(void) override; + + // demuxflush + // + // Flushes the demultiplexer + void demuxflush(void) override; + + // demuxread + // + // Reads the next packet from the demultiplexer + DEMUX_PACKET* demuxread(std::function const& allocator) override; + + // demuxreset + // + // Resets the demultiplexer + void demuxreset(void) override; + + // devicename + // + // Gets the device name associated with the stream + std::string devicename(void) const override; + + // enumproperties + // + // Enumerates the stream properties + void enumproperties( + std::function const& callback) override; + + // length + // + // Gets the length of the stream + long long length(void) const override; + + // muxname + // + // Gets the mux name associated with the stream + std::string muxname(void) const override; + + // position + // + // Gets the current position of the stream + long long position(void) const override; + + // read + // + // Reads available data from the stream + size_t read(uint8_t* buffer, size_t count) override; + + // realtime + // + // Gets a flag indicating if the stream is real-time + bool realtime(void) const override; + + // seek + // + // Sets the stream pointer to a specific position + long long seek(long long position, int whence) override; + + // servicename + // + // Gets the service name associated with the stream + std::string servicename(void) const override; + + // signalquality + // + // Gets the signal quality as percentages + void signalquality(int& quality, int& snr) const override; + +private: + dabstream(dabstream const&) = delete; + dabstream& operator=(dabstream const&) = delete; + + // DEFAULT_AUDIO_RATE + // + // The default audio output sample rate + static int const DEFAULT_AUDIO_RATE; + + // MAX_PACKET_QUEUE + // + // Maximum number of queued demux packets + static size_t const MAX_PACKET_QUEUE; + + // RING_BUFFER_SIZE + // + // Input ring buffer size + static size_t const RING_BUFFER_SIZE; + + // SAMPLE_RATE + // + // Fixed device sample rate required for DAB + static uint32_t const SAMPLE_RATE; + + // STREAM_ID_AUDIOBASE + // + // Base stream identifier for the audio output stream + static int const STREAM_ID_AUDIOBASE; + + // STREAM_ID_ID3TAG + // + // Stream identifier for the ID3v2 tag output stream + static int const STREAM_ID_ID3TAG; + + // Instance Constructor + // + dabstream(std::unique_ptr device, + struct tunerprops const& tunerprops, + struct channelprops const& channelprops, + struct dabprops const& dabprops, + uint32_t subchannel); + + //----------------------------------------------------------------------- + // Private Type Declarations + + // demux_packet_t + // + // Defines the conents of a queued demux packet + struct demux_packet_t + { + + int streamid = 0; + int size = 0; + double duration = 0; + double dts = 0; + double pts = 0; + std::unique_ptr data; + }; + + // demux_queue_t + // + // Defines the type of the demux queue + using demux_queue_t = std::queue>; + + // eventid_t + // + // Defines a worker thread event identifier + enum class eventid_t + { + + InputFailure, // An input failure has occurred + ServiceDetected, // A new service has been detected + }; + + // event_queue_t + // + // Defines the type of the worker thread event queue + using event_queue_t = std::queue; + + //----------------------------------------------------------------------- + // Private Member Functions + + // worker + // + // Worker thread procedure used to transfer and process data + void worker(scalar_condition& started); + + //----------------------------------------------------------------------- + // InputInterface Implementation + + // getSamples + // + // Reads the specified number of samples from the input device + int32_t getSamples(DSPCOMPLEX* buffer, int32_t size) override; + + // getSamplesToRead + // + // Gets the number of input samples that are available to read from input + int32_t getSamplesToRead(void) override; + + // is_ok + // + // Determines if the input is still "OK" + bool is_ok(void) override; + + // restart + // + // Restarts the input + bool restart(void) override; + + //----------------------------------------------------------------------- + // ProgrammeHandlerInterface + + // onNewAudio + // + // Invoked when a new packet of audio data has been decoded + void onNewAudio(std::vector&& audioData, + int sampleRate, + const std::string& mode) override; + + // onNewDynamicLabel + // + // Invoked when a new dynamic label has been decoded + void onNewDynamicLabel(const std::string& label) override; + + // onMOT + // + // Invoked when a new slide has been decoded + void onMOT(const mot_file_t& mot_file) override; + + //----------------------------------------------------------------------- + // RadioControllerInterface + + // onFrequencyCorrectorChange + // + // Invoked when the frequency correction has been changed + void onFrequencyCorrectorChange(int fine, int coarse) override; + + // onInputFailure + // + // Invoked when the receiver has shut down to an input failure + void onInputFailure(void) override; + + // onServiceDetected + // + // Invoked when a new service was detected + void onServiceDetected(uint32_t sId) override; + + // onSetEnsembleLabel + // + // Invoked when the ensemble label has changed + void onSetEnsembleLabel(DabLabel& label) override; + + // onSNR + // + // Invoked when the Signal-to-Nosie Radio has been calculated + void onSNR(float snr) override; + + // onSyncChange + // + // Invoked when signal synchronization was acquired or lost + void onSyncChange(bool isSync) override; + + //----------------------------------------------------------------------- + // Member Variables + + std::unique_ptr m_device; // RTL-SDR device instance + aligned_ptr m_receiver; // RadioReceiver instance + RingBuffer m_ringbuffer; // I/Q sample ring buffer + + // STREAM CONTROL + // + uint32_t const m_subchannel; // Ensemble subchannel number + float const m_pcmgain; // Output gain + std::atomic m_streamok{true}; // "OK" flag for the stream + double m_dts{STREAM_TIME_BASE}; // Current decode time stamp + std::atomic m_audioid{STREAM_ID_AUDIOBASE}; // Current audio stream id + std::atomic m_audiorate{DEFAULT_AUDIO_RATE}; // Current audio output rate + + // DEMUX QUEUE + // + demux_queue_t m_queue; // queue<> of demux objects + mutable std::mutex m_queuelock; // Synchronization object + std::condition_variable m_queuecv; // Event condition variable + + // WORKER THREAD + // + std::thread m_worker; // Data transfer thread + std::exception_ptr m_worker_exception; // Exception on worker thread + scalar_condition m_stop{false}; // Condition to stop data transfer + std::atomic m_stopped{false}; // Data transfer stopped flag + event_queue_t m_events; // queue<> of worker events + mutable std::mutex m_eventslock; // Synchronization object +}; + +//----------------------------------------------------------------------------- + +#pragma warning(pop) + +#endif // __HDSTREAM_H_ diff --git a/src/database.cpp b/src/database.cpp index 4448535..484ecb8 100644 --- a/src/database.cpp +++ b/src/database.cpp @@ -1,1767 +1,1767 @@ -//--------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//--------------------------------------------------------------------------- - -#include "database.h" - -#include "dbtypes.h" -#include "sqlite_exception.h" -#include "stdafx.h" - -#include - -#pragma warning(push, 4) - -//--------------------------------------------------------------------------- -// FUNCTION PROTOTYPES -//--------------------------------------------------------------------------- - -static void bind_parameter(sqlite3_stmt* statement, int& paramindex, const char* value); -static void bind_parameter(sqlite3_stmt* statement, int& paramindex, unsigned int value); -template -static int execute_non_query(sqlite3* instance, char const* sql, _parameters&&... parameters); -template -static int execute_scalar_int(sqlite3* instance, char const* sql, _parameters&&... parameters); -template -static std::string execute_scalar_string(sqlite3* instance, - char const* sql, - _parameters&&... parameters); - -//--------------------------------------------------------------------------- -// CONNECTIONPOOL IMPLEMENTATION -//--------------------------------------------------------------------------- - -//--------------------------------------------------------------------------- -// connectionpool Constructor -// -// Arguments: -// -// connstring - Database connection string -// poolsize - Initial connection pool size -// flags - Database connection flags - -connectionpool::connectionpool(char const* connstring, size_t poolsize, int flags) - : m_connstr((connstring) ? connstring : ""), m_flags(flags) -{ - sqlite3* handle = nullptr; // Initial database connection - - if (connstring == nullptr) - throw std::invalid_argument("connstring"); - - // Create and pool an initial connection to initialize the database - handle = open_database(m_connstr.c_str(), m_flags, true); - m_connections.push_back(handle); - m_queue.push(handle); - - // Create and pool the requested number of additional connections - try - { - - for (size_t index = 1; index < poolsize; index++) - { - - handle = open_database(m_connstr.c_str(), m_flags, false); - m_connections.push_back(handle); - m_queue.push(handle); - } - } - - catch (...) - { - - // Clear the connection cache and destroy all created connections - while (!m_queue.empty()) - m_queue.pop(); - for (auto const& iterator : m_connections) - close_database(iterator); - - throw; - } -} - -//--------------------------------------------------------------------------- -// connectionpool Destructor - -connectionpool::~connectionpool() -{ - // Close all of the connections that were created in the pool - for (auto const& iterator : m_connections) - close_database(iterator); -} - -//--------------------------------------------------------------------------- -// connectionpool::acquire -// -// Acquires a database connection, opening a new one if necessary -// -// Arguments: -// -// NONE - -sqlite3* connectionpool::acquire(void) -{ - sqlite3* handle = nullptr; // Handle to return to the caller - - std::unique_lock lock(m_lock); - - if (m_queue.empty()) - { - - // No connections are available, open a new one using the same flags - handle = open_database(m_connstr.c_str(), m_flags, false); - m_connections.push_back(handle); - } - - // At least one connection is available for reuse - else - { - handle = m_queue.front(); - m_queue.pop(); - } - - return handle; -} - -//--------------------------------------------------------------------------- -// connectionpool::release -// -// Releases a database handle acquired from the pool -// -// Arguments: -// -// handle - Handle to be releases - -void connectionpool::release(sqlite3* handle) -{ - std::unique_lock lock(m_lock); - - if (handle == nullptr) - throw std::invalid_argument("handle"); - - m_queue.push(handle); -} - -//--------------------------------------------------------------------------- -// add_channel -// -// Adds a new channel to the database -// -// Arguments: -// -// instance - Database instance -// channelprops - Channel properties - -bool add_channel(sqlite3* instance, struct channelprops const& channelprops) -{ - // frequency | modulation | name | autogain | manualgain | freqcorrection | logourl - return execute_non_query(instance, "replace into channel values(?1, ?2, ?3, ?4, ?5, ?6, ?7)", - channelprops.frequency, static_cast(channelprops.modulation), - channelprops.name.c_str(), (channelprops.autogain) ? 1 : 0, - channelprops.manualgain, channelprops.freqcorrection, - channelprops.logourl.c_str()) > 0; -} - -//--------------------------------------------------------------------------- -// add_channel -// -// Adds a new channel to the database -// -// Arguments: -// -// instance - Database instance -// channelprops - Channel properties -// subchannelprops - Subchannel properties - -bool add_channel(sqlite3* instance, - struct channelprops const& channelprops, - std::vector const& subchannelprops) -{ - bool result = false; // Result code to return to caller - - try - { - - // Clone the subchannel table schema into a temporary table - execute_non_query(instance, "drop table if exists subchannel_temp"); - execute_non_query(instance, - "create temp table subchannel_temp as select * from subchannel limit 0"); - - // frequency | number | modulation | name | logourl - for (auto const& it : subchannelprops) - { - - execute_non_query(instance, "insert into subchannel_temp values(?1, ?2, ?3, ?4, ?5)", - channelprops.frequency, it.number, - static_cast(channelprops.modulation), it.name.c_str(), - channelprops.logourl.c_str()); - } - - // This requires a multi-step operation; start a transaction - execute_non_query(instance, "begin immediate transaction"); - - try - { - - // frequency | modulation | name | autogain | manualgain | freqcorrection | logourl - result = - execute_non_query(instance, "replace into channel values(?1, ?2, ?3, ?4, ?5, ?6, ?7)", - channelprops.frequency, static_cast(channelprops.modulation), - channelprops.name.c_str(), (channelprops.autogain) ? 1 : 0, - channelprops.manualgain, channelprops.freqcorrection, - channelprops.logourl.c_str()) > 0; - - if (result) - { - - // Remove subchannels that no longer exist for this channel - execute_non_query(instance, - "delete from subchannel where frequency = ?1 and modulation = ?2 " - "and number not in (select number from subchannel_temp)", - channelprops.frequency, static_cast(channelprops.modulation)); - - // Use an upsert operation to insert/update the remaining subchannels, only replace the name if necessary - execute_non_query( - instance, - "insert into subchannel select * from subchannel_temp where true " - "on conflict(frequency, modulation, number) do update set name=excluded.name"); - } - - // Commit the database transaction - execute_non_query(instance, "commit transaction"); - } - - // Rollback the transaction on any exception - catch (...) - { - try_execute_non_query(instance, "rollback transaction"); - throw; - } - - // Drop the temporary table - execute_non_query(instance, "drop table subchannel_temp"); - } - - // Drop the temporary table on any exception - catch (...) - { - execute_non_query(instance, "drop table subchannel_temp"); - throw; - } - - return result; -} - -//--------------------------------------------------------------------------- -// bind_parameter (local) -// -// Used by execute_non_query to bind a string parameter -// -// Arguments: -// -// statement - SQL statement instance -// paramindex - Index of the parameter to bind; will be incremented -// value - Value to bind as the parameter - -static void bind_parameter(sqlite3_stmt* statement, int& paramindex, char const* value) -{ - int result; // Result from binding operation - - // If a null string pointer was provided, bind it as NULL instead of TEXT - if (value == nullptr) - result = sqlite3_bind_null(statement, paramindex++); - else - result = sqlite3_bind_text(statement, paramindex++, value, -1, SQLITE_STATIC); - - if (result != SQLITE_OK) - throw sqlite_exception(result); -} - -//--------------------------------------------------------------------------- -// bind_parameter (local) -// -// Used by execute_non_query to bind an integer parameter -// -// Arguments: -// -// statement - SQL statement instance -// paramindex - Index of the parameter to bind; will be incremented -// value - Value to bind as the parameter - -static void bind_parameter(sqlite3_stmt* statement, int& paramindex, int value) -{ - int result = sqlite3_bind_int(statement, paramindex++, value); - if (result != SQLITE_OK) - throw sqlite_exception(result); -} - -//--------------------------------------------------------------------------- -// bind_parameter (local) -// -// Used by execute_non_query to bind an integer parameter -// -// Arguments: -// -// statement - SQL statement instance -// paramindex - Index of the parameter to bind; will be incremented -// value - Value to bind as the parameter - -static void bind_parameter(sqlite3_stmt* statement, int& paramindex, unsigned int value) -{ - int result = sqlite3_bind_int64(statement, paramindex++, static_cast(value)); - if (result != SQLITE_OK) - throw sqlite_exception(result); -} - -//--------------------------------------------------------------------------- -// channel_exists -// -// Determines if a channel exists in the database -// -// Arguments: -// -// instance - Database instance -// channelprops - Channel properties - -bool channel_exists(sqlite3* instance, struct channelprops const& channelprops) -{ - if (instance == nullptr) - throw std::invalid_argument("instance"); - - return execute_scalar_int( - instance, - "select exists(select * from channel where frequency = ?1 and modulation = ?2)", - channelprops.frequency, static_cast(channelprops.modulation)) == 1; -} - -//--------------------------------------------------------------------------- -// clear_channels -// -// Clears all channels from the database -// -// Arguments: -// -// instance - Database instance - -void clear_channels(sqlite3* instance) -{ - if (instance == nullptr) - throw std::invalid_argument("instance"); - - execute_non_query(instance, "delete from channel"); -} - -//--------------------------------------------------------------------------- -// close_database -// -// Closes a SQLite database handle -// -// Arguments: -// -// instance - Database instance handle to be closed - -void close_database(sqlite3* instance) -{ - if (instance) - sqlite3_close(instance); -} - -//--------------------------------------------------------------------------- -// delete_channel -// -// Deletes a channel from the database -// -// Arguments: -// -// instance - Database instance -// frequency - Frequency of the channel to be deleted -// modulation - Modulation of the channel to be deleted - -void delete_channel(sqlite3* instance, uint32_t frequency, enum modulation modulation) -{ - if (instance == nullptr) - throw std::invalid_argument("instance"); - - // This requires a multi-step operation; start a transaction - execute_non_query(instance, "begin immediate transaction"); - - try - { - - // Remove subchannels prior to removing the channel - execute_non_query(instance, "delete from subchannel where frequency = ?1 and modulation = ?2", - frequency, static_cast(modulation)); - - // Remove the channel - execute_non_query(instance, "delete from channel where frequency = ?1 and modulation = ?2", - frequency, static_cast(modulation)); - - // Commit the database transaction - execute_non_query(instance, "commit transaction"); - } - - // Rollback the transaction on any exception - catch (...) - { - try_execute_non_query(instance, "rollback transaction"); - throw; - } -} - -//--------------------------------------------------------------------------- -// delete_subchannel -// -// Deletes a subchannel from the database -// -// Arguments: -// -// instance - Database instance -// frequency - Frequency of the channel to be deleted -// modulation - Modulation of the channel to be deleted -// number - Subchannel number to be deleted - -void delete_subchannel(sqlite3* instance, - uint32_t frequency, - enum modulation modulation, - uint32_t number) -{ - if (instance == nullptr) - throw std::invalid_argument("instance"); - - // This requires a multi-step operation; start a transaction - execute_non_query(instance, "begin immediate transaction"); - - try - { - - // Remove the specified subchannel - execute_non_query( - instance, "delete from subchannel where frequency = ?1 and number = ?2 and modulation = ?3", - frequency, number, static_cast(modulation)); - - // If there are no more subchannels for this channel, delete the parent channel as well - if (execute_scalar_int( - instance, - "select count(number) from subchannel where frequency = ?1 and modulation = ?2", - frequency, static_cast(modulation)) == 0) - { - - execute_non_query(instance, "delete from channel where frequency = ?1 and modulation = ?2", - frequency, static_cast(modulation)); - } - - // Commit the database transaction - execute_non_query(instance, "commit transaction"); - } - - // Rollback the transaction on any exception - catch (...) - { - try_execute_non_query(instance, "rollback transaction"); - throw; - } -} - -//--------------------------------------------------------------------------- -// enumerate_dabradio_channels -// -// Enumerates DAB channels -// -// Arguments: -// -// instance - Database instance -// callback - Callback function - -void enumerate_dabradio_channels(sqlite3* instance, enumerate_channels_callback const& callback) -{ - sqlite3_stmt* statement; // SQL statement to execute - int result; // Result from SQLite function - - if (instance == nullptr) - throw std::invalid_argument("instance"); - - // - // DAB Band III channel numbers are arbitrarily assigned in the range of 301 (5A) through 338 (13F) based - // on the content of the (static) namedchannel table - // - - // frequency | channelnumber | subchannelnumber | name | logourl - auto sql = "select channel.frequency as frequency, namedchannel.number as channelnumber, " - "ifnull(subchannel.number, 0) as subchannelnumber, " - "ifnull(subchannel.name, channel.name) as name, ifnull(subchannel.logourl, " - "channel.logourl) as logourl " - "from channel inner join namedchannel on channel.frequency = namedchannel.frequency " - "and channel.modulation = namedchannel.modulation " - "left outer join subchannel on channel.frequency = subchannel.frequency and " - "channel.modulation = subchannel.modulation " - "where channel.modulation = 2 order by channelnumber, subchannelnumber asc"; - - result = sqlite3_prepare_v2(instance, sql, -1, &statement, nullptr); - if (result != SQLITE_OK) - throw sqlite_exception(result, sqlite3_errmsg(instance)); - - try - { - - // Execute the query and iterate over all returned rows - while (sqlite3_step(statement) == SQLITE_ROW) - { - - struct channel item = {}; - channelid channelid(sqlite3_column_int(statement, 0), sqlite3_column_int(statement, 2), - modulation::dab); - - item.id = channelid.id(); - item.channel = sqlite3_column_int(statement, 1); - item.subchannel = sqlite3_column_int(statement, 2); - item.name = reinterpret_cast(sqlite3_column_text(statement, 3)); - item.logourl = reinterpret_cast(sqlite3_column_text(statement, 4)); - - callback(item); // Invoke caller-supplied callback - } - - sqlite3_finalize(statement); // Finalize the SQLite statement - } - - catch (...) - { - sqlite3_finalize(statement); - throw; - } -} - -//--------------------------------------------------------------------------- -// enumerate_fmradio_channels -// -// Enumerates FM Radio channels -// -// Arguments: -// -// instance - Database instance -// prependnumber - Flag to prepend the channel number to the name -// callback - Callback function - -void enumerate_fmradio_channels(sqlite3* instance, - bool prependnumber, - enumerate_channels_callback const& callback) -{ - sqlite3_stmt* statement; // SQL statement to execute - int result; // Result from SQLite function - - if (instance == nullptr) - throw std::invalid_argument("instance"); - - // - // FM Radio channel/subchannel numbers are assigned in the range of 87.5 through 108.0 based on the - // channel frequency in Megahertz - // - - // frequency | channelnumber | subchannelnumber | name | logourl - auto sql = - "select channel.frequency as frequency, channel.frequency / 1000000 as channelnumber, " - "(channel.frequency % 1000000) / 100000 as subchannelnumber, " - "case ?1 when 0 then channel.name else cast(channel.frequency / 1000000 as text) || '.' || " - "cast((channel.frequency % 1000000) / 100000 as text) || ' ' || channel.name end as name, " - "channel.logourl as logourl from channel where channel.modulation = 0 order by " - "channelnumber, subchannelnumber asc"; - - result = sqlite3_prepare_v2(instance, sql, -1, &statement, nullptr); - if (result != SQLITE_OK) - throw sqlite_exception(result, sqlite3_errmsg(instance)); - - try - { - - // Bind the query parameters - result = sqlite3_bind_int(statement, 1, (prependnumber) ? 1 : 0); - if (result != SQLITE_OK) - throw sqlite_exception(result); - - // Execute the query and iterate over all returned rows - while (sqlite3_step(statement) == SQLITE_ROW) - { - - struct channel item = {}; - channelid channelid(sqlite3_column_int(statement, 0), modulation::fm); - - item.id = channelid.id(); - item.channel = sqlite3_column_int(statement, 1); - item.subchannel = sqlite3_column_int(statement, 2); - item.name = reinterpret_cast(sqlite3_column_text(statement, 3)); - item.logourl = reinterpret_cast(sqlite3_column_text(statement, 4)); - - callback(item); // Invoke caller-supplied callback - } - - sqlite3_finalize(statement); // Finalize the SQLite statement - } - - catch (...) - { - sqlite3_finalize(statement); - throw; - } -} - -//--------------------------------------------------------------------------- -// enumerate_hdradio_channels -// -// Enumerates HD Radio channels -// -// Arguments: -// -// instance - Database instance -// prependnumber - Flag to prepend the channel number to the name -// callback - Callback function - -void enumerate_hdradio_channels(sqlite3* instance, - bool prependnumber, - enumerate_channels_callback const& callback) -{ - sqlite3_stmt* statement; // SQL statement to execute - int result; // Result from SQLite function - - if (instance == nullptr) - throw std::invalid_argument("instance"); - - // - // HD Radio channel numbers are assigned in the range of 200-300 based on - // https://en.wikipedia.org/wiki/List_of_channel_numbers_assigned_to_FM_frequencies_in_North_America - // - - // frequency | channelnumber | subchannelnumber | name | logourl - auto sql = "select channel.frequency as frequency, (((channel.frequency / 100000) - 879) / 2) + " - "200 as channelnumber, " - "ifnull(subchannel.number, 0) as subchannelnumber, " - "case ?1 when 0 then '' else cast(channel.frequency / 1000000 as text) || '.' || " - "cast((channel.frequency % 1000000) / 100000 as text) || ' ' end || " - " channel.name || iif(subchannel.name is null, '', ' ' || subchannel.name) as name, " - "ifnull(subchannel.logourl, channel.logourl) as logourl " - "from channel left outer join subchannel on channel.frequency = subchannel.frequency " - "and channel.modulation = subchannel.modulation " - "where channel.modulation = 1 order by channelnumber, subchannelnumber asc"; - - result = sqlite3_prepare_v2(instance, sql, -1, &statement, nullptr); - if (result != SQLITE_OK) - throw sqlite_exception(result, sqlite3_errmsg(instance)); - - try - { - - // Bind the query parameters - result = sqlite3_bind_int(statement, 1, (prependnumber) ? 1 : 0); - if (result != SQLITE_OK) - throw sqlite_exception(result); - - // Execute the query and iterate over all returned rows - while (sqlite3_step(statement) == SQLITE_ROW) - { - - struct channel item = {}; - channelid channelid(sqlite3_column_int(statement, 0), sqlite3_column_int(statement, 2), - modulation::hd); - - item.id = channelid.id(); - item.channel = sqlite3_column_int(statement, 1); - item.subchannel = sqlite3_column_int(statement, 2); - item.name = reinterpret_cast(sqlite3_column_text(statement, 3)); - item.logourl = reinterpret_cast(sqlite3_column_text(statement, 4)); - - callback(item); // Invoke caller-supplied callback - } - - sqlite3_finalize(statement); // Finalize the SQLite statement - } - - catch (...) - { - sqlite3_finalize(statement); - throw; - } -} - -//--------------------------------------------------------------------------- -// enumerate_namedchannels -// -// Enumerates the named channels for a specific modulation -// -// Arguments: -// -// instance - Database instance -// modulation - Modulation type -// callback - Callback function - -void enumerate_namedchannels(sqlite3* instance, - enum modulation modulation, - enumerate_namedchannels_callback const& callback) -{ - sqlite3_stmt* statement; // SQL statement to execute - int result; // Result from SQLite function - - if (instance == nullptr) - throw std::invalid_argument("instance"); - - // frequency | name - auto sql = "select frequency as frequency, name as name from namedchannel where modulation = ?1 " - "order by number asc"; - - result = sqlite3_prepare_v2(instance, sql, -1, &statement, nullptr); - if (result != SQLITE_OK) - throw sqlite_exception(result, sqlite3_errmsg(instance)); - - try - { - - // Bind the query parameters - result = sqlite3_bind_int(statement, 1, static_cast(modulation)); - if (result != SQLITE_OK) - throw sqlite_exception(result); - - // Execute the query and iterate over all returned rows - while (sqlite3_step(statement) == SQLITE_ROW) - { - - struct namedchannel item = {}; - - item.frequency = static_cast(sqlite3_column_int64(statement, 0)); - item.name = reinterpret_cast(sqlite3_column_text(statement, 1)); - - callback(item); // Invoke caller-supplied callback - } - - sqlite3_finalize(statement); // Finalize the SQLite statement - } - - catch (...) - { - sqlite3_finalize(statement); - throw; - } -} - -//--------------------------------------------------------------------------- -// enumerate_rawfiles -// -// Enumerates available raw files registered in the database -// -// Arguments: -// -// instance - Database instance -// callback - Callback function - -void enumerate_rawfiles(sqlite3* instance, enumerate_rawfiles_callback const& callback) -{ - sqlite3_stmt* statement; // SQL statement to execute - int result; // Result from SQLite function - - if (instance == nullptr) - throw std::invalid_argument("instance"); - - // path | name | samplerate - auto sql = "select path as path, name || ' (' || cast(samplerate as text) || ')' as name, " - "samplerate as samplerate from rawfile order by name, samplerate asc"; - - result = sqlite3_prepare_v2(instance, sql, -1, &statement, nullptr); - if (result != SQLITE_OK) - throw sqlite_exception(result, sqlite3_errmsg(instance)); - - try - { - - // Execute the query and iterate over all returned rows - while (sqlite3_step(statement) == SQLITE_ROW) - { - - struct rawfile item = {}; - - item.path = reinterpret_cast(sqlite3_column_text(statement, 0)); - item.name = reinterpret_cast(sqlite3_column_text(statement, 1)); - item.samplerate = static_cast(sqlite3_column_int64(statement, 2)); - - callback(item); // Invoke caller-supplied callback - } - - sqlite3_finalize(statement); // Finalize the SQLite statement - } - - catch (...) - { - sqlite3_finalize(statement); - throw; - } -} - -//--------------------------------------------------------------------------- -// enumerate_wxradio_channels -// -// Enumerates Weather Radio channels -// -// Arguments: -// -// instance - Database instance -// callback - Callback function - -void enumerate_wxradio_channels(sqlite3* instance, enumerate_channels_callback const& callback) -{ - sqlite3_stmt* statement; // SQL statement to execute - int result; // Result from SQLite function - - if (instance == nullptr) - throw std::invalid_argument("instance"); - - // - // Weather Radio channel numbers are arbitrarily assigned in the range of 401 (WX1) through 407 (WX7) based - // on the content of the (static) namedchannel table - // - - // frequency | channelnumber | subchannelnumber | name | logourl - auto sql = "select channel.frequency as frequency, namedchannel.number as channelnumber, 0 as " - "subchannelnumber, " - "channel.name as name, channel.logourl as logourl from channel inner join " - "namedchannel on channel.frequency = namedchannel.frequency " - "and channel.modulation = namedchannel.modulation where channel.modulation = 3 order " - "by channelnumber, subchannelnumber asc"; - - result = sqlite3_prepare_v2(instance, sql, -1, &statement, nullptr); - if (result != SQLITE_OK) - throw sqlite_exception(result, sqlite3_errmsg(instance)); - - try - { - - // Execute the query and iterate over all returned rows - while (sqlite3_step(statement) == SQLITE_ROW) - { - - struct channel item = {}; - channelid channelid(sqlite3_column_int(statement, 0), modulation::wx); - - item.id = channelid.id(); - item.channel = sqlite3_column_int(statement, 1); - item.subchannel = sqlite3_column_int(statement, 2); - item.name = reinterpret_cast(sqlite3_column_text(statement, 3)); - item.logourl = reinterpret_cast(sqlite3_column_text(statement, 4)); - - callback(item); // Invoke caller-supplied callback - } - - sqlite3_finalize(statement); // Finalize the SQLite statement - } - - catch (...) - { - sqlite3_finalize(statement); - throw; - } -} - -//--------------------------------------------------------------------------- -// execute_non_query (local) -// -// Executes a database query and returns the number of rows affected -// -// Arguments: -// -// instance - Database instance -// sql - SQL query to execute -// parameters - Parameters to be bound to the query - -template -static int execute_non_query(sqlite3* instance, char const* sql, _parameters&&... parameters) -{ - sqlite3_stmt* statement; // SQL statement to execute - int paramindex = 1; // Bound parameter index value - - if (instance == nullptr) - throw std::invalid_argument("instance"); - if (sql == nullptr) - throw std::invalid_argument("sql"); - - // Suppress unreferenced local variable warning when there are no parameters to bind - (void)paramindex; - - // Prepare the statement - int result = sqlite3_prepare_v2(instance, sql, -1, &statement, nullptr); - if (result != SQLITE_OK) - throw sqlite_exception(result, sqlite3_errmsg(instance)); - - try - { - - // Bind the provided query parameter(s) by unpacking the parameter pack - int unpack[] = {0, - (static_cast(bind_parameter(statement, paramindex, parameters)), 0)...}; - (void)unpack; - - // Execute the query; ignore any rows that are returned - do - result = sqlite3_step(statement); - while (result == SQLITE_ROW); - - // The final result from sqlite3_step should be SQLITE_DONE - if (result != SQLITE_DONE) - throw sqlite_exception(result, sqlite3_errmsg(instance)); - - // Finalize the statement - sqlite3_finalize(statement); - - // Return the number of changes made by the statement - return sqlite3_changes(instance); - } - - catch (...) - { - sqlite3_finalize(statement); - throw; - } -} - -//--------------------------------------------------------------------------- -// execute_scalar_int (local) -// -// Executes a database query and returns a scalar integer result -// -// Arguments: -// -// instance - Database instance -// sql - SQL query to execute -// parameters - Parameters to be bound to the query - -template -static int execute_scalar_int(sqlite3* instance, char const* sql, _parameters&&... parameters) -{ - sqlite3_stmt* statement; // SQL statement to execute - int paramindex = 1; // Bound parameter index value - int value = 0; // Result from the scalar function - - if (instance == nullptr) - throw std::invalid_argument("instance"); - if (sql == nullptr) - throw std::invalid_argument("sql"); - - // Suppress unreferenced local variable warning when there are no parameters to bind - (void)paramindex; - - // Prepare the statement - int result = sqlite3_prepare_v2(instance, sql, -1, &statement, nullptr); - if (result != SQLITE_OK) - throw sqlite_exception(result, sqlite3_errmsg(instance)); - - try - { - - // Bind the provided query parameter(s) by unpacking the parameter pack - int unpack[] = {0, - (static_cast(bind_parameter(statement, paramindex, parameters)), 0)...}; - (void)unpack; - - // Execute the query; only the first row returned will be used - result = sqlite3_step(statement); - - if (result == SQLITE_ROW) - value = sqlite3_column_int(statement, 0); - else if (result != SQLITE_DONE) - throw sqlite_exception(result, sqlite3_errmsg(instance)); - - // Finalize the statement - sqlite3_finalize(statement); - - // Return the resultant value from the scalar query - return value; - } - - catch (...) - { - sqlite3_finalize(statement); - throw; - } -} - -//--------------------------------------------------------------------------- -// execute_scalar_string (local) -// -// Executes a database query and returns a scalar string result -// -// Arguments: -// -// instance - Database instance -// sql - SQL query to execute -// parameters - Parameters to be bound to the query - -template -static std::string execute_scalar_string(sqlite3* instance, - char const* sql, - _parameters&&... parameters) -{ - sqlite3_stmt* statement; // SQL statement to execute - int paramindex = 1; // Bound parameter index value - std::string value; // Result from the scalar function - - if (instance == nullptr) - throw std::invalid_argument("instance"); - if (sql == nullptr) - throw std::invalid_argument("sql"); - - // Suppress unreferenced local variable warning when there are no parameters to bind - (void)paramindex; - - // Prepare the statement - int result = sqlite3_prepare_v2(instance, sql, -1, &statement, nullptr); - if (result != SQLITE_OK) - throw sqlite_exception(result, sqlite3_errmsg(instance)); - - try - { - - // Bind the provided query parameter(s) by unpacking the parameter pack - int unpack[] = {0, - (static_cast(bind_parameter(statement, paramindex, parameters)), 0)...}; - (void)unpack; - - // Execute the query; only the first row returned will be used - result = sqlite3_step(statement); - - if (result == SQLITE_ROW) - { - - char const* ptr = reinterpret_cast(sqlite3_column_text(statement, 0)); - if (ptr != nullptr) - value.assign(ptr); - } - else if (result != SQLITE_DONE) - throw sqlite_exception(result, sqlite3_errmsg(instance)); - - // Finalize the statement - sqlite3_finalize(statement); - - // Return the resultant value from the scalar query - return value; - } - - catch (...) - { - sqlite3_finalize(statement); - throw; - } -} - -//--------------------------------------------------------------------------- -// export_channels -// -// Exports the channels into a JSON file -// -// Arguments: -// -// instance - SQLite database instance -// path - Path to the output file to generate - -std::string export_channels(sqlite3* instance) -{ - if (instance == nullptr) - throw std::invalid_argument("instance"); - - return execute_scalar_string( - instance, "select json_group_array(json_object(" - "'frequency', frequency, 'modulation', case modulation when 0 then 'FM' when 1 " - "then 'HD' when 2 then 'DAB' when 3 then 'WX' else 'FM' end, " - "'name', name, 'autogain', autogain, 'manualgain', manualgain, 'freqcorrection', " - "freqcorrection, 'logourl', logourl)) " - "from channel"); -} - -//--------------------------------------------------------------------------- -// get_channel_count -// -// Gets the number of available channels in the database -// -// Arguments: -// -// instance - SQLite database instance - -int get_channel_count(sqlite3* instance) -{ - if (instance == nullptr) - return 0; - - // Use the same LEFT OUTER JOIN logic against subchannel to return an accurate count when a digital - // channel does not have any defined subchannels and will return a .0 channel instance - return execute_scalar_int( - instance, - "select count(*) from channel left outer join subchannel " - "on channel.frequency = subchannel.frequency and channel.modulation = subchannel.modulation"); -} - -//--------------------------------------------------------------------------- -// get_channel_properties -// -// Gets the tuning properties of a channel from the database -// -// Arguments: -// -// instance - SQLite database instance -// frequency - Channel frequency -// modulation - Channel modulation -// id - Channel unique identifier -// channelprops - Structure to receive the channel properties - -bool get_channel_properties(sqlite3* instance, - uint32_t frequency, - enum modulation modulation, - struct channelprops& channelprops) -{ - sqlite3_stmt* statement; // SQL statement to execute - int result; // Result from SQLite function - bool found = false; // Flag if channel was found in database - - if (instance == nullptr) - throw std::invalid_argument("instance"); - - // name | autogain | manualgain | freqcorrection | logourl - auto sql = "select name, autogain, manualgain, freqcorrection, logourl from channel where " - "frequency = ?1 and modulation = ?2"; - - result = sqlite3_prepare_v2(instance, sql, -1, &statement, nullptr); - if (result != SQLITE_OK) - throw sqlite_exception(result, sqlite3_errmsg(instance)); - - try - { - - // Bind the query parameters - result = sqlite3_bind_int64(statement, 1, static_cast(frequency)); - if (result == SQLITE_OK) - sqlite3_bind_int(statement, 2, static_cast(modulation)); - if (result != SQLITE_OK) - throw sqlite_exception(result); - - // Execute the query; there should be one and only one row returned - if (sqlite3_step(statement) == SQLITE_ROW) - { - - channelprops.frequency = frequency; - channelprops.modulation = modulation; - - unsigned char const* name = sqlite3_column_text(statement, 0); - channelprops.name.assign((name == nullptr) ? "" : reinterpret_cast(name)); - - channelprops.autogain = (sqlite3_column_int(statement, 1) != 0); - channelprops.manualgain = sqlite3_column_int(statement, 2); - channelprops.freqcorrection = sqlite3_column_int(statement, 3); - - unsigned char const* logourl = sqlite3_column_text(statement, 4); - channelprops.logourl.assign((logourl == nullptr) ? "" - : reinterpret_cast(logourl)); - - found = true; // Channel was found in the database - } - - sqlite3_finalize(statement); // Finalize the SQLite statement - } - - catch (...) - { - sqlite3_finalize(statement); - throw; - } - - return found; -} - -//--------------------------------------------------------------------------- -// get_channel_properties -// -// Gets the tuning properties of a channel from the database -// -// Arguments: -// -// instance - SQLite database instance -// frequency - Channel frequency -// modulation - Channel modulation -// id - Channel unique identifier -// channelprops - Structure to receive the channel properties -// subchannelprops - vector<> containing subchannel properties - -bool get_channel_properties(sqlite3* instance, - uint32_t frequency, - enum modulation modulation, - struct channelprops& channelprops, - std::vector& subchannelprops) -{ - sqlite3_stmt* statement; // SQL statement to execute - int result; // Result from SQLite function - - if (instance == nullptr) - throw std::invalid_argument("instance"); - - // Get the parent channel properties before enumerating the subchannels - if (!get_channel_properties(instance, frequency, modulation, channelprops)) - return false; - - subchannelprops.clear(); // Reset the vector<> - - // number | name | logourl - auto sql = "select number, name, logourl from subchannel where frequency = ?1 and modulation = " - "?2 order by number"; - - result = sqlite3_prepare_v2(instance, sql, -1, &statement, nullptr); - if (result != SQLITE_OK) - throw sqlite_exception(result, sqlite3_errmsg(instance)); - - try - { - - // Bind the query parameters - result = sqlite3_bind_int64(statement, 1, static_cast(frequency)); - if (result == SQLITE_OK) - sqlite3_bind_int(statement, 2, static_cast(modulation)); - if (result != SQLITE_OK) - throw sqlite_exception(result); - - // Execute the query and iterate over all returned rows - while (sqlite3_step(statement) == SQLITE_ROW) - { - - struct subchannelprops subchannel = {}; - - subchannel.number = sqlite3_column_int(statement, 0); - - unsigned char const* name = sqlite3_column_text(statement, 1); - subchannel.name.assign((name == nullptr) ? "" : reinterpret_cast(name)); - - unsigned char const* logourl = sqlite3_column_text(statement, 2); - subchannel.logourl.assign((logourl == nullptr) ? "" : reinterpret_cast(logourl)); - - subchannelprops.emplace_back(std::move(subchannel)); - } - - sqlite3_finalize(statement); // Finalize the SQLite statement - } - - catch (...) - { - sqlite3_finalize(statement); - throw; - } - - return true; -} - -//--------------------------------------------------------------------------- -// has_rawfiles -// -// Gets a flag indicating if there are raw input files available to use -// -// Arguments: -// -// instance - Database instance - -bool has_rawfiles(sqlite3* instance) -{ - if (instance == nullptr) - throw std::invalid_argument("instance"); - - return execute_scalar_int(instance, "select exists(select path from rawfile)") != 0; -} - -//--------------------------------------------------------------------------- -// import_channels -// -// Imports channels from a JSON string -// -// Arguments: -// -// instance - Database instance -// json - JSON data to be imported - -void import_channels(sqlite3* instance, char const* json) -{ - if (instance == nullptr) - throw std::invalid_argument("instance"); - if ((json == nullptr) || (*json == '\0')) - throw std::invalid_argument("instance"); - - // - // TODO: Add a 'channel' element that can replace frequency/modulation for named channels. - // The JSON will likely need to be put into a temp table anyway to deal with subchannels - // so this entire operation will likely need to be redone regardless - // - - // Massage the input as much as possible, only the frequency is actually required, - // the rest can be defaulted if not present. Also watch out for duplicates and the frequency range - execute_non_query( - instance, - "replace into channel " - "select cast(json_extract(entry.value, '$.frequency') as integer) as frequency, " - "case upper(cast(ifnull(json_extract(entry.value, '$.modulation'), '') as text)) " - " when 'FM' then 0 " - " when 'FMRADIO' then 0 " - " when 'HD' then 1 " - " when 'HDRADIO' then 1 " - " when 'DAB' then 2 " - " when 'DAB+' then 2 " - " when 'WX' then 3 " - " when 'WEATHER' then 3 " - " else case " - " when cast(json_extract(entry.value, '$.frequency') as integer) between 174928000 and " - "239200000 then 2 " // DAB - " when cast(json_extract(entry.value, '$.frequency') as integer) between 162400000 and " - "162550000 then 3 " // WX - " else 0 end " // FM - " end as modulation, " - "cast(ifnull(json_extract(entry.value, '$.name'), '') as text) as name, " - "cast(ifnull(json_extract(entry.value, '$.autogain'), 0) as integer) as autogain, " - "cast(ifnull(json_extract(entry.value, '$.manualgain'), 0) as integer) as manualgain, " - "cast(ifnull(json_extract(entry.value, '$.freqcorrection'), 0) as integer) as " - "freqcorrection, " - "json_extract(entry.value, '$.logourl') as logourl " // <-- this one allows nulls - "from json_each(?1) as entry " - "where frequency is not null and " - " ((frequency between 87500000 and 108000000) or " // FM / HD - " (frequency between 174928000 and 239200000) or " // DAB - " (frequency between 162400000 and 162550000)) " // WX - " and modulation between 0 and 3 " - "group by frequency, modulation", - json); - - // Remove any FM channels that are outside the frequency range - execute_non_query(instance, "delete from channel where modulation = 0 and " - "frequency not between 87500000 and 108000000"); - - // Remove any HD Radio channels that are outside the frequency range - execute_non_query( - instance, "delete from channel where modulation = 0 and " - "(frequency not between 87900000 and 107900000 or (frequency / 100000) % 2 = 0)"); - - // Remove any DAB channels that don't match an entry in namedchannel - execute_non_query(instance, - "delete from channel where modulation = 2 and " - "frequency not in(select frequency from namedchannel where modulation = 2)"); - - // Remove any Weather Radio channels that don't match an entry in namedchannel - execute_non_query(instance, - "delete from channel where modulation = 3 and " - "frequency not in(select frequency from namedchannel where modulation = 3)"); -} - -//--------------------------------------------------------------------------- -// open_database -// -// Opens the SQLite database instance -// -// Arguments: -// -// connstring - Database connection string -// flags - Database open flags (see sqlite3_open_v2) - -sqlite3* open_database(char const* connstring, int flags) -{ - return open_database(connstring, flags, false); -} - -//--------------------------------------------------------------------------- -// open_database -// -// Opens the SQLite database instance -// -// Arguments: -// -// connstring - Database connection string -// flags - Database open flags (see sqlite3_open_v2) -// initialize - Flag indicating database schema should be (re)initialized - -sqlite3* open_database(char const* connstring, int flags, bool initialize) -{ - sqlite3* instance = nullptr; // SQLite database instance - - // Create the database using the provided connection string - int result = sqlite3_open_v2(connstring, &instance, flags, nullptr); - if (result != SQLITE_OK) - throw sqlite_exception(result); - - // set the connection to report extended error codes - sqlite3_extended_result_codes(instance, -1); - - // set a busy_timeout handler for this connection - sqlite3_busy_timeout(instance, 5000); - - try - { - - // switch the database to write-ahead logging - // - execute_non_query(instance, "pragma journal_mode=wal"); - - // Only execute schema creation steps if the database is being initialized; the caller needs - // to ensure that this is set for only one connection otherwise locking issues can occur - if (initialize) - { - - // get the database schema version - // - int dbversion = execute_scalar_int(instance, "pragma user_version"); - - // SCHEMA VERSION 0 -> VERSION 1 - // - if (dbversion == 0) - { - - // table: channel - // - // frequency(pk) | subchannel(pk) | hidden | name | autogain | manualgain | freqcorrection | logourl - execute_non_query(instance, "drop table if exists channel"); - execute_non_query( - instance, - "create table channel(frequency integer not null, subchannel integer not null, " - "hidden integer not null, name text not null, autogain integer not null, manualgain " - "integer not null, freqcorrection integer not null, " - "logourl text null, primary key(frequency, subchannel))"); - - execute_non_query(instance, "pragma user_version = 1"); - dbversion = 1; - } - - // SCHEMA VERSION 1 -> VERSION 2 - // - if (dbversion == 1) - { - - // table: channel_v1 - // - // frequency(pk) | subchannel(pk) | hidden | name | autogain | manualgain | logourl - execute_non_query(instance, "alter table channel rename to channel_v1"); - - // table: channel - // - // frequency(pk) | subchannel(pk) | modulation (pk) | hidden | name | autogain | manualgain | freqcorrection | logourl - execute_non_query(instance, - "create table channel(frequency integer not null, subchannel integer not " - "null, modulation integer not null, " - "hidden integer not null, name text not null, autogain integer not null, " - "manualgain integer not null, freqcorrection integer not null, " - "logourl text null, primary key(frequency, subchannel, modulation))"); - - // Version 1 modulation can be gleaned from the frequency, anything between 162.400MHz and 162.550MHz is WX (3), anything else is FM (0) - execute_non_query( - instance, - "insert into channel select v1.frequency, v1.subchannel, case when (v1.frequency >= " - "162400000 and v1.frequency <= 162550000) then 3 else 0 end, " - "v1.hidden, v1.name, v1.autogain, v1.manualgain, 0, v1.logourl from channel_v1 as v1"); - - execute_non_query(instance, "drop table channel_v1"); - execute_non_query(instance, "pragma user_version = 2"); - dbversion = 2; - } - - // SCHEMA VERSION 2 -> VERSION 3 - // - if (dbversion == 2) - { - - // table: channel_v2 - // - // frequency(pk) | subchannel(pk) | modulation (pk) | hidden | name | autogain | manualgain | freqcorrection | logourl - execute_non_query(instance, "alter table channel rename to channel_v2"); - - // table: channel - // - // frequency(pk) | modulation (pk) | name | autogain | manualgain | freqcorrection | logourl - execute_non_query( - instance, - "create table channel(frequency integer not null, modulation integer not null, " - "name text not null, autogain integer not null, manualgain integer not null, " - "freqcorrection integer not null, " - "logourl text null, primary key(frequency, modulation))"); - - // There shouldn't be any channels with a subchannel number in the previous version of the table, but clean them - // out during the upgrade just in case to avoid possible primary key violation on the reload - execute_non_query(instance, "delete from channel_v2 where subchannel != 0"); - execute_non_query(instance, "insert into channel select v2.frequency, v2.modulation, " - "v2.name, v2.autogain, v2.manualgain, " - "v2.freqcorrection, v2.logourl from channel_v2 as v2"); - - // table: subchannel - // - // frequency(pk) | subchannel(pk) | modulation (pk) | name - execute_non_query(instance, "drop table if exists subchannel"); - execute_non_query( - instance, - "create table subchannel(frequency integer not null, number integer not null, " - "modulation integer not null, " - "name text not null, logourl null, primary key(frequency, number, modulation))"); - - execute_non_query(instance, "drop table channel_v2"); - - // table: rawfile - // - // path(pk) | name | samplerate - execute_non_query(instance, "drop table if exists rawfile"); - execute_non_query(instance, "create table rawfile(path text not null, name text not null, " - "samplerate integer not null, primary key(path))"); - - // table: namedchannel - // - // frequency(pk) | modulation(pk) | name | number - execute_non_query(instance, "drop table if exists namedchannel"); - execute_non_query( - instance, - "create table namedchannel(frequency integer not null, modulation integer not null, " - "name text not null, number not null, primary key(frequency, modulation))"); - - // DAB Band III - // - execute_non_query(instance, "insert into namedchannel values(174928000, 2, '5A', 301)"); - execute_non_query(instance, "insert into namedchannel values(176640000, 2, '5B', 302)"); - execute_non_query(instance, "insert into namedchannel values(178352000, 2, '5C', 303)"); - execute_non_query(instance, "insert into namedchannel values(180064000, 2, '5D', 304)"); - execute_non_query(instance, "insert into namedchannel values(181936000, 2, '6A', 305)"); - execute_non_query(instance, "insert into namedchannel values(183648000, 2, '6B', 306)"); - execute_non_query(instance, "insert into namedchannel values(185360000, 2, '6C', 307)"); - execute_non_query(instance, "insert into namedchannel values(187072000, 2, '6D', 308)"); - execute_non_query(instance, "insert into namedchannel values(188928000, 2, '7A', 309)"); - execute_non_query(instance, "insert into namedchannel values(190640000, 2, '7B', 310)"); - execute_non_query(instance, "insert into namedchannel values(192352000, 2, '7C', 311)"); - execute_non_query(instance, "insert into namedchannel values(194064000, 2, '7D', 312)"); - execute_non_query(instance, "insert into namedchannel values(195936000, 2, '8A', 313)"); - execute_non_query(instance, "insert into namedchannel values(197648000, 2, '8B', 314)"); - execute_non_query(instance, "insert into namedchannel values(199360000, 2, '8C', 315)"); - execute_non_query(instance, "insert into namedchannel values(201072000, 2, '8D', 316)"); - execute_non_query(instance, "insert into namedchannel values(202928000, 2, '9A', 317)"); - execute_non_query(instance, "insert into namedchannel values(204640000, 2, '9B', 318)"); - execute_non_query(instance, "insert into namedchannel values(206352000, 2, '9C', 319)"); - execute_non_query(instance, "insert into namedchannel values(208064000, 2, '9D', 320)"); - execute_non_query(instance, "insert into namedchannel values(209936000, 2, '10A', 321)"); - execute_non_query(instance, "insert into namedchannel values(211648000, 2, '10B', 322)"); - execute_non_query(instance, "insert into namedchannel values(213360000, 2, '10C', 323)"); - execute_non_query(instance, "insert into namedchannel values(215072000, 2, '10D', 324)"); - execute_non_query(instance, "insert into namedchannel values(216928000, 2, '11A', 325)"); - execute_non_query(instance, "insert into namedchannel values(218640000, 2, '11B', 326)"); - execute_non_query(instance, "insert into namedchannel values(220352000, 2, '11C', 327)"); - execute_non_query(instance, "insert into namedchannel values(222064000, 2, '11D', 328)"); - execute_non_query(instance, "insert into namedchannel values(223936000, 2, '12A', 329)"); - execute_non_query(instance, "insert into namedchannel values(225648000, 2, '12B', 330)"); - execute_non_query(instance, "insert into namedchannel values(227360000, 2, '12C', 331)"); - execute_non_query(instance, "insert into namedchannel values(229072000, 2, '12D', 332)"); - execute_non_query(instance, "insert into namedchannel values(230784000, 2, '13A', 333)"); - execute_non_query(instance, "insert into namedchannel values(232496000, 2, '13B', 334)"); - execute_non_query(instance, "insert into namedchannel values(234208000, 2, '13C', 335)"); - execute_non_query(instance, "insert into namedchannel values(235776000, 2, '13D', 336)"); - execute_non_query(instance, "insert into namedchannel values(237488000, 2, '13E', 337)"); - execute_non_query(instance, "insert into namedchannel values(239200000, 2, '13F', 338)"); - - // Weather Radio - // - execute_non_query(instance, "insert into namedchannel values(162400000, 3, 'WX2', 402)"); - execute_non_query(instance, "insert into namedchannel values(162425000, 3, 'WX4', 404)"); - execute_non_query(instance, "insert into namedchannel values(162450000, 3, 'WX5', 405)"); - execute_non_query(instance, "insert into namedchannel values(162475000, 3, 'WX3', 403)"); - execute_non_query(instance, "insert into namedchannel values(162500000, 3, 'WX6', 406)"); - execute_non_query(instance, "insert into namedchannel values(162525000, 3, 'WX7', 407)"); - execute_non_query(instance, "insert into namedchannel values(162550000, 3, 'WX1', 401)"); - - // Remove any Weather Radio channels that don't match an entry in namedchannel - execute_non_query( - instance, "delete from channel where modulation = 3 and " - "frequency not in(select frequency from namedchannel where modulation = 3)"); - - execute_non_query(instance, "pragma user_version = 3"); - dbversion = 3; - } - } - } - - // Close the database instance on any thrown exceptions - catch (...) - { - sqlite3_close(instance); - throw; - } - - return instance; -} - -//--------------------------------------------------------------------------- -// rename_channel -// -// Renames a channel in the database -// -// Arguments: -// -// instance - Database instance -// frequency - Frequency of the channel to be renamed -// modulation - Modulation of the channel to be renamed -// newname - New name to assign to the channel - -void rename_channel(sqlite3* instance, - uint32_t frequency, - enum modulation modulation, - char const* newname) -{ - if (instance == nullptr) - throw std::invalid_argument("instance"); - - execute_non_query(instance, - "update channel set name = ?1 where frequency = ?2 and modulation = ?3", - (newname == nullptr) ? "" : newname, frequency, static_cast(modulation)); -} - -//--------------------------------------------------------------------------- -// try_execute_non_query -// -// Executes a non-query against the database and eats any exceptions -// -// Arguments: -// -// instance - Database instance -// sql - SQL non-query to execute - -bool try_execute_non_query(sqlite3* instance, char const* sql) -{ - try - { - execute_non_query(instance, sql); - } - catch (...) - { - return false; - } - - return true; -} - -//--------------------------------------------------------------------------- -// update_channel -// -// Updates the tuning properties of a channel in the database -// -// Arguments: -// -// instance - SQLite database instance -// channelprops - Structure containing the updated channel properties - -bool update_channel(sqlite3* instance, struct channelprops const& channelprops) -{ - if (instance == nullptr) - throw std::invalid_argument("instance"); - - return execute_non_query(instance, - "update channel set name = ?1, autogain = ?2, manualgain = ?3, " - "freqcorrection = ?4, logourl = ?5 " - "where frequency = ?6 and modulation = ?7", - channelprops.name.c_str(), (channelprops.autogain) ? 1 : 0, - channelprops.manualgain, channelprops.freqcorrection, - channelprops.logourl.c_str(), channelprops.frequency, - static_cast(channelprops.modulation)) > 0; -} - -//--------------------------------------------------------------------------- -// update_channel -// -// Updates the tuning properties of a channel in the database -// -// Arguments: -// -// instance - SQLite database instance -// channelprops - Structure containing the updated channel properties -// subchannelprops - vector<> containing updated subchannel properties - -bool update_channel(sqlite3* instance, - struct channelprops const& channelprops, - std::vector const& subchannelprops) -{ - bool result = false; // Result code to return to caller - - try - { - - // Clone the subchannel table schema into a temporary table - execute_non_query(instance, "drop table if exists subchannel_temp"); - execute_non_query(instance, - "create temp table subchannel_temp as select * from subchannel limit 0"); - - // frequency | subchannel | modulation | name | logourl - for (auto const& it : subchannelprops) - { - - execute_non_query(instance, "insert into subchannel_temp values(?1, ?2, ?3, ?4, ?5)", - channelprops.frequency, it.number, - static_cast(channelprops.modulation), it.name.c_str(), - channelprops.logourl.c_str()); - } - - // This requires a multi-step operation; start a transaction - execute_non_query(instance, "begin immediate transaction"); - - try - { - - // Update the base channel properties - result = execute_non_query(instance, - "update channel set name = ?1, autogain = ?2, manualgain = ?3, " - "freqcorrection = ?4, logourl = ?5 " - "where frequency = ?6 and modulation = ?7", - channelprops.name.c_str(), (channelprops.autogain) ? 1 : 0, - channelprops.manualgain, channelprops.freqcorrection, - channelprops.logourl.c_str(), channelprops.frequency, - static_cast(channelprops.modulation)) > 0; - - if (result) - { - - // Remove subchannels that no longer exist for this channel - execute_non_query(instance, - "delete from subchannel where frequency = ?1 and modulation = ?2 " - "and number not in (select number from subchannel_temp)", - channelprops.frequency, static_cast(channelprops.modulation)); - - // Use an upsert operation to insert/update the remaining subchannels, only replace the name if necessary - execute_non_query( - instance, - "insert into subchannel select * from subchannel_temp where true " - "on conflict(frequency, modulation, number) do update set name=excluded.name"); - } - - // Commit the database transaction - execute_non_query(instance, "commit transaction"); - } - - // Rollback the transaction on any exception - catch (...) - { - try_execute_non_query(instance, "rollback transaction"); - throw; - } - - // Drop the temporary table - execute_non_query(instance, "drop table subchannel_temp"); - } - - // Drop the temporary table on any exception - catch (...) - { - execute_non_query(instance, "drop table subchannel_temp"); - throw; - } - - return result; -} - -//--------------------------------------------------------------------------- - -#pragma warning(pop) +//--------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//--------------------------------------------------------------------------- + +#include "database.h" + +#include "dbtypes.h" +#include "sqlite_exception.h" +#include "stdafx.h" + +#include + +#pragma warning(push, 4) + +//--------------------------------------------------------------------------- +// FUNCTION PROTOTYPES +//--------------------------------------------------------------------------- + +static void bind_parameter(sqlite3_stmt* statement, int& paramindex, const char* value); +static void bind_parameter(sqlite3_stmt* statement, int& paramindex, unsigned int value); +template +static int execute_non_query(sqlite3* instance, char const* sql, _parameters&&... parameters); +template +static int execute_scalar_int(sqlite3* instance, char const* sql, _parameters&&... parameters); +template +static std::string execute_scalar_string(sqlite3* instance, + char const* sql, + _parameters&&... parameters); + +//--------------------------------------------------------------------------- +// CONNECTIONPOOL IMPLEMENTATION +//--------------------------------------------------------------------------- + +//--------------------------------------------------------------------------- +// connectionpool Constructor +// +// Arguments: +// +// connstring - Database connection string +// poolsize - Initial connection pool size +// flags - Database connection flags + +connectionpool::connectionpool(char const* connstring, size_t poolsize, int flags) + : m_connstr((connstring) ? connstring : ""), m_flags(flags) +{ + sqlite3* handle = nullptr; // Initial database connection + + if (connstring == nullptr) + throw std::invalid_argument("connstring"); + + // Create and pool an initial connection to initialize the database + handle = open_database(m_connstr.c_str(), m_flags, true); + m_connections.push_back(handle); + m_queue.push(handle); + + // Create and pool the requested number of additional connections + try + { + + for (size_t index = 1; index < poolsize; index++) + { + + handle = open_database(m_connstr.c_str(), m_flags, false); + m_connections.push_back(handle); + m_queue.push(handle); + } + } + + catch (...) + { + + // Clear the connection cache and destroy all created connections + while (!m_queue.empty()) + m_queue.pop(); + for (auto const& iterator : m_connections) + close_database(iterator); + + throw; + } +} + +//--------------------------------------------------------------------------- +// connectionpool Destructor + +connectionpool::~connectionpool() +{ + // Close all of the connections that were created in the pool + for (auto const& iterator : m_connections) + close_database(iterator); +} + +//--------------------------------------------------------------------------- +// connectionpool::acquire +// +// Acquires a database connection, opening a new one if necessary +// +// Arguments: +// +// NONE + +sqlite3* connectionpool::acquire(void) +{ + sqlite3* handle = nullptr; // Handle to return to the caller + + std::unique_lock lock(m_lock); + + if (m_queue.empty()) + { + + // No connections are available, open a new one using the same flags + handle = open_database(m_connstr.c_str(), m_flags, false); + m_connections.push_back(handle); + } + + // At least one connection is available for reuse + else + { + handle = m_queue.front(); + m_queue.pop(); + } + + return handle; +} + +//--------------------------------------------------------------------------- +// connectionpool::release +// +// Releases a database handle acquired from the pool +// +// Arguments: +// +// handle - Handle to be releases + +void connectionpool::release(sqlite3* handle) +{ + std::unique_lock lock(m_lock); + + if (handle == nullptr) + throw std::invalid_argument("handle"); + + m_queue.push(handle); +} + +//--------------------------------------------------------------------------- +// add_channel +// +// Adds a new channel to the database +// +// Arguments: +// +// instance - Database instance +// channelprops - Channel properties + +bool add_channel(sqlite3* instance, struct channelprops const& channelprops) +{ + // frequency | modulation | name | autogain | manualgain | freqcorrection | logourl + return execute_non_query(instance, "replace into channel values(?1, ?2, ?3, ?4, ?5, ?6, ?7)", + channelprops.frequency, static_cast(channelprops.modulation), + channelprops.name.c_str(), (channelprops.autogain) ? 1 : 0, + channelprops.manualgain, channelprops.freqcorrection, + channelprops.logourl.c_str()) > 0; +} + +//--------------------------------------------------------------------------- +// add_channel +// +// Adds a new channel to the database +// +// Arguments: +// +// instance - Database instance +// channelprops - Channel properties +// subchannelprops - Subchannel properties + +bool add_channel(sqlite3* instance, + struct channelprops const& channelprops, + std::vector const& subchannelprops) +{ + bool result = false; // Result code to return to caller + + try + { + + // Clone the subchannel table schema into a temporary table + execute_non_query(instance, "drop table if exists subchannel_temp"); + execute_non_query(instance, + "create temp table subchannel_temp as select * from subchannel limit 0"); + + // frequency | number | modulation | name | logourl + for (auto const& it : subchannelprops) + { + + execute_non_query(instance, "insert into subchannel_temp values(?1, ?2, ?3, ?4, ?5)", + channelprops.frequency, it.number, + static_cast(channelprops.modulation), it.name.c_str(), + channelprops.logourl.c_str()); + } + + // This requires a multi-step operation; start a transaction + execute_non_query(instance, "begin immediate transaction"); + + try + { + + // frequency | modulation | name | autogain | manualgain | freqcorrection | logourl + result = + execute_non_query(instance, "replace into channel values(?1, ?2, ?3, ?4, ?5, ?6, ?7)", + channelprops.frequency, static_cast(channelprops.modulation), + channelprops.name.c_str(), (channelprops.autogain) ? 1 : 0, + channelprops.manualgain, channelprops.freqcorrection, + channelprops.logourl.c_str()) > 0; + + if (result) + { + + // Remove subchannels that no longer exist for this channel + execute_non_query(instance, + "delete from subchannel where frequency = ?1 and modulation = ?2 " + "and number not in (select number from subchannel_temp)", + channelprops.frequency, static_cast(channelprops.modulation)); + + // Use an upsert operation to insert/update the remaining subchannels, only replace the name if necessary + execute_non_query( + instance, + "insert into subchannel select * from subchannel_temp where true " + "on conflict(frequency, modulation, number) do update set name=excluded.name"); + } + + // Commit the database transaction + execute_non_query(instance, "commit transaction"); + } + + // Rollback the transaction on any exception + catch (...) + { + try_execute_non_query(instance, "rollback transaction"); + throw; + } + + // Drop the temporary table + execute_non_query(instance, "drop table subchannel_temp"); + } + + // Drop the temporary table on any exception + catch (...) + { + execute_non_query(instance, "drop table subchannel_temp"); + throw; + } + + return result; +} + +//--------------------------------------------------------------------------- +// bind_parameter (local) +// +// Used by execute_non_query to bind a string parameter +// +// Arguments: +// +// statement - SQL statement instance +// paramindex - Index of the parameter to bind; will be incremented +// value - Value to bind as the parameter + +static void bind_parameter(sqlite3_stmt* statement, int& paramindex, char const* value) +{ + int result; // Result from binding operation + + // If a null string pointer was provided, bind it as NULL instead of TEXT + if (value == nullptr) + result = sqlite3_bind_null(statement, paramindex++); + else + result = sqlite3_bind_text(statement, paramindex++, value, -1, SQLITE_STATIC); + + if (result != SQLITE_OK) + throw sqlite_exception(result); +} + +//--------------------------------------------------------------------------- +// bind_parameter (local) +// +// Used by execute_non_query to bind an integer parameter +// +// Arguments: +// +// statement - SQL statement instance +// paramindex - Index of the parameter to bind; will be incremented +// value - Value to bind as the parameter + +static void bind_parameter(sqlite3_stmt* statement, int& paramindex, int value) +{ + int result = sqlite3_bind_int(statement, paramindex++, value); + if (result != SQLITE_OK) + throw sqlite_exception(result); +} + +//--------------------------------------------------------------------------- +// bind_parameter (local) +// +// Used by execute_non_query to bind an integer parameter +// +// Arguments: +// +// statement - SQL statement instance +// paramindex - Index of the parameter to bind; will be incremented +// value - Value to bind as the parameter + +static void bind_parameter(sqlite3_stmt* statement, int& paramindex, unsigned int value) +{ + int result = sqlite3_bind_int64(statement, paramindex++, static_cast(value)); + if (result != SQLITE_OK) + throw sqlite_exception(result); +} + +//--------------------------------------------------------------------------- +// channel_exists +// +// Determines if a channel exists in the database +// +// Arguments: +// +// instance - Database instance +// channelprops - Channel properties + +bool channel_exists(sqlite3* instance, struct channelprops const& channelprops) +{ + if (instance == nullptr) + throw std::invalid_argument("instance"); + + return execute_scalar_int( + instance, + "select exists(select * from channel where frequency = ?1 and modulation = ?2)", + channelprops.frequency, static_cast(channelprops.modulation)) == 1; +} + +//--------------------------------------------------------------------------- +// clear_channels +// +// Clears all channels from the database +// +// Arguments: +// +// instance - Database instance + +void clear_channels(sqlite3* instance) +{ + if (instance == nullptr) + throw std::invalid_argument("instance"); + + execute_non_query(instance, "delete from channel"); +} + +//--------------------------------------------------------------------------- +// close_database +// +// Closes a SQLite database handle +// +// Arguments: +// +// instance - Database instance handle to be closed + +void close_database(sqlite3* instance) +{ + if (instance) + sqlite3_close(instance); +} + +//--------------------------------------------------------------------------- +// delete_channel +// +// Deletes a channel from the database +// +// Arguments: +// +// instance - Database instance +// frequency - Frequency of the channel to be deleted +// modulation - Modulation of the channel to be deleted + +void delete_channel(sqlite3* instance, uint32_t frequency, enum modulation modulation) +{ + if (instance == nullptr) + throw std::invalid_argument("instance"); + + // This requires a multi-step operation; start a transaction + execute_non_query(instance, "begin immediate transaction"); + + try + { + + // Remove subchannels prior to removing the channel + execute_non_query(instance, "delete from subchannel where frequency = ?1 and modulation = ?2", + frequency, static_cast(modulation)); + + // Remove the channel + execute_non_query(instance, "delete from channel where frequency = ?1 and modulation = ?2", + frequency, static_cast(modulation)); + + // Commit the database transaction + execute_non_query(instance, "commit transaction"); + } + + // Rollback the transaction on any exception + catch (...) + { + try_execute_non_query(instance, "rollback transaction"); + throw; + } +} + +//--------------------------------------------------------------------------- +// delete_subchannel +// +// Deletes a subchannel from the database +// +// Arguments: +// +// instance - Database instance +// frequency - Frequency of the channel to be deleted +// modulation - Modulation of the channel to be deleted +// number - Subchannel number to be deleted + +void delete_subchannel(sqlite3* instance, + uint32_t frequency, + enum modulation modulation, + uint32_t number) +{ + if (instance == nullptr) + throw std::invalid_argument("instance"); + + // This requires a multi-step operation; start a transaction + execute_non_query(instance, "begin immediate transaction"); + + try + { + + // Remove the specified subchannel + execute_non_query( + instance, "delete from subchannel where frequency = ?1 and number = ?2 and modulation = ?3", + frequency, number, static_cast(modulation)); + + // If there are no more subchannels for this channel, delete the parent channel as well + if (execute_scalar_int( + instance, + "select count(number) from subchannel where frequency = ?1 and modulation = ?2", + frequency, static_cast(modulation)) == 0) + { + + execute_non_query(instance, "delete from channel where frequency = ?1 and modulation = ?2", + frequency, static_cast(modulation)); + } + + // Commit the database transaction + execute_non_query(instance, "commit transaction"); + } + + // Rollback the transaction on any exception + catch (...) + { + try_execute_non_query(instance, "rollback transaction"); + throw; + } +} + +//--------------------------------------------------------------------------- +// enumerate_dabradio_channels +// +// Enumerates DAB channels +// +// Arguments: +// +// instance - Database instance +// callback - Callback function + +void enumerate_dabradio_channels(sqlite3* instance, enumerate_channels_callback const& callback) +{ + sqlite3_stmt* statement; // SQL statement to execute + int result; // Result from SQLite function + + if (instance == nullptr) + throw std::invalid_argument("instance"); + + // + // DAB Band III channel numbers are arbitrarily assigned in the range of 301 (5A) through 338 (13F) based + // on the content of the (static) namedchannel table + // + + // frequency | channelnumber | subchannelnumber | name | logourl + auto sql = "select channel.frequency as frequency, namedchannel.number as channelnumber, " + "ifnull(subchannel.number, 0) as subchannelnumber, " + "ifnull(subchannel.name, channel.name) as name, ifnull(subchannel.logourl, " + "channel.logourl) as logourl " + "from channel inner join namedchannel on channel.frequency = namedchannel.frequency " + "and channel.modulation = namedchannel.modulation " + "left outer join subchannel on channel.frequency = subchannel.frequency and " + "channel.modulation = subchannel.modulation " + "where channel.modulation = 2 order by channelnumber, subchannelnumber asc"; + + result = sqlite3_prepare_v2(instance, sql, -1, &statement, nullptr); + if (result != SQLITE_OK) + throw sqlite_exception(result, sqlite3_errmsg(instance)); + + try + { + + // Execute the query and iterate over all returned rows + while (sqlite3_step(statement) == SQLITE_ROW) + { + + struct channel item = {}; + channelid channelid(sqlite3_column_int(statement, 0), sqlite3_column_int(statement, 2), + modulation::dab); + + item.id = channelid.id(); + item.channel = sqlite3_column_int(statement, 1); + item.subchannel = sqlite3_column_int(statement, 2); + item.name = reinterpret_cast(sqlite3_column_text(statement, 3)); + item.logourl = reinterpret_cast(sqlite3_column_text(statement, 4)); + + callback(item); // Invoke caller-supplied callback + } + + sqlite3_finalize(statement); // Finalize the SQLite statement + } + + catch (...) + { + sqlite3_finalize(statement); + throw; + } +} + +//--------------------------------------------------------------------------- +// enumerate_fmradio_channels +// +// Enumerates FM Radio channels +// +// Arguments: +// +// instance - Database instance +// prependnumber - Flag to prepend the channel number to the name +// callback - Callback function + +void enumerate_fmradio_channels(sqlite3* instance, + bool prependnumber, + enumerate_channels_callback const& callback) +{ + sqlite3_stmt* statement; // SQL statement to execute + int result; // Result from SQLite function + + if (instance == nullptr) + throw std::invalid_argument("instance"); + + // + // FM Radio channel/subchannel numbers are assigned in the range of 87.5 through 108.0 based on the + // channel frequency in Megahertz + // + + // frequency | channelnumber | subchannelnumber | name | logourl + auto sql = + "select channel.frequency as frequency, channel.frequency / 1000000 as channelnumber, " + "(channel.frequency % 1000000) / 100000 as subchannelnumber, " + "case ?1 when 0 then channel.name else cast(channel.frequency / 1000000 as text) || '.' || " + "cast((channel.frequency % 1000000) / 100000 as text) || ' ' || channel.name end as name, " + "channel.logourl as logourl from channel where channel.modulation = 0 order by " + "channelnumber, subchannelnumber asc"; + + result = sqlite3_prepare_v2(instance, sql, -1, &statement, nullptr); + if (result != SQLITE_OK) + throw sqlite_exception(result, sqlite3_errmsg(instance)); + + try + { + + // Bind the query parameters + result = sqlite3_bind_int(statement, 1, (prependnumber) ? 1 : 0); + if (result != SQLITE_OK) + throw sqlite_exception(result); + + // Execute the query and iterate over all returned rows + while (sqlite3_step(statement) == SQLITE_ROW) + { + + struct channel item = {}; + channelid channelid(sqlite3_column_int(statement, 0), modulation::fm); + + item.id = channelid.id(); + item.channel = sqlite3_column_int(statement, 1); + item.subchannel = sqlite3_column_int(statement, 2); + item.name = reinterpret_cast(sqlite3_column_text(statement, 3)); + item.logourl = reinterpret_cast(sqlite3_column_text(statement, 4)); + + callback(item); // Invoke caller-supplied callback + } + + sqlite3_finalize(statement); // Finalize the SQLite statement + } + + catch (...) + { + sqlite3_finalize(statement); + throw; + } +} + +//--------------------------------------------------------------------------- +// enumerate_hdradio_channels +// +// Enumerates HD Radio channels +// +// Arguments: +// +// instance - Database instance +// prependnumber - Flag to prepend the channel number to the name +// callback - Callback function + +void enumerate_hdradio_channels(sqlite3* instance, + bool prependnumber, + enumerate_channels_callback const& callback) +{ + sqlite3_stmt* statement; // SQL statement to execute + int result; // Result from SQLite function + + if (instance == nullptr) + throw std::invalid_argument("instance"); + + // + // HD Radio channel numbers are assigned in the range of 200-300 based on + // https://en.wikipedia.org/wiki/List_of_channel_numbers_assigned_to_FM_frequencies_in_North_America + // + + // frequency | channelnumber | subchannelnumber | name | logourl + auto sql = "select channel.frequency as frequency, (((channel.frequency / 100000) - 879) / 2) + " + "200 as channelnumber, " + "ifnull(subchannel.number, 0) as subchannelnumber, " + "case ?1 when 0 then '' else cast(channel.frequency / 1000000 as text) || '.' || " + "cast((channel.frequency % 1000000) / 100000 as text) || ' ' end || " + " channel.name || iif(subchannel.name is null, '', ' ' || subchannel.name) as name, " + "ifnull(subchannel.logourl, channel.logourl) as logourl " + "from channel left outer join subchannel on channel.frequency = subchannel.frequency " + "and channel.modulation = subchannel.modulation " + "where channel.modulation = 1 order by channelnumber, subchannelnumber asc"; + + result = sqlite3_prepare_v2(instance, sql, -1, &statement, nullptr); + if (result != SQLITE_OK) + throw sqlite_exception(result, sqlite3_errmsg(instance)); + + try + { + + // Bind the query parameters + result = sqlite3_bind_int(statement, 1, (prependnumber) ? 1 : 0); + if (result != SQLITE_OK) + throw sqlite_exception(result); + + // Execute the query and iterate over all returned rows + while (sqlite3_step(statement) == SQLITE_ROW) + { + + struct channel item = {}; + channelid channelid(sqlite3_column_int(statement, 0), sqlite3_column_int(statement, 2), + modulation::hd); + + item.id = channelid.id(); + item.channel = sqlite3_column_int(statement, 1); + item.subchannel = sqlite3_column_int(statement, 2); + item.name = reinterpret_cast(sqlite3_column_text(statement, 3)); + item.logourl = reinterpret_cast(sqlite3_column_text(statement, 4)); + + callback(item); // Invoke caller-supplied callback + } + + sqlite3_finalize(statement); // Finalize the SQLite statement + } + + catch (...) + { + sqlite3_finalize(statement); + throw; + } +} + +//--------------------------------------------------------------------------- +// enumerate_namedchannels +// +// Enumerates the named channels for a specific modulation +// +// Arguments: +// +// instance - Database instance +// modulation - Modulation type +// callback - Callback function + +void enumerate_namedchannels(sqlite3* instance, + enum modulation modulation, + enumerate_namedchannels_callback const& callback) +{ + sqlite3_stmt* statement; // SQL statement to execute + int result; // Result from SQLite function + + if (instance == nullptr) + throw std::invalid_argument("instance"); + + // frequency | name + auto sql = "select frequency as frequency, name as name from namedchannel where modulation = ?1 " + "order by number asc"; + + result = sqlite3_prepare_v2(instance, sql, -1, &statement, nullptr); + if (result != SQLITE_OK) + throw sqlite_exception(result, sqlite3_errmsg(instance)); + + try + { + + // Bind the query parameters + result = sqlite3_bind_int(statement, 1, static_cast(modulation)); + if (result != SQLITE_OK) + throw sqlite_exception(result); + + // Execute the query and iterate over all returned rows + while (sqlite3_step(statement) == SQLITE_ROW) + { + + struct namedchannel item = {}; + + item.frequency = static_cast(sqlite3_column_int64(statement, 0)); + item.name = reinterpret_cast(sqlite3_column_text(statement, 1)); + + callback(item); // Invoke caller-supplied callback + } + + sqlite3_finalize(statement); // Finalize the SQLite statement + } + + catch (...) + { + sqlite3_finalize(statement); + throw; + } +} + +//--------------------------------------------------------------------------- +// enumerate_rawfiles +// +// Enumerates available raw files registered in the database +// +// Arguments: +// +// instance - Database instance +// callback - Callback function + +void enumerate_rawfiles(sqlite3* instance, enumerate_rawfiles_callback const& callback) +{ + sqlite3_stmt* statement; // SQL statement to execute + int result; // Result from SQLite function + + if (instance == nullptr) + throw std::invalid_argument("instance"); + + // path | name | samplerate + auto sql = "select path as path, name || ' (' || cast(samplerate as text) || ')' as name, " + "samplerate as samplerate from rawfile order by name, samplerate asc"; + + result = sqlite3_prepare_v2(instance, sql, -1, &statement, nullptr); + if (result != SQLITE_OK) + throw sqlite_exception(result, sqlite3_errmsg(instance)); + + try + { + + // Execute the query and iterate over all returned rows + while (sqlite3_step(statement) == SQLITE_ROW) + { + + struct rawfile item = {}; + + item.path = reinterpret_cast(sqlite3_column_text(statement, 0)); + item.name = reinterpret_cast(sqlite3_column_text(statement, 1)); + item.samplerate = static_cast(sqlite3_column_int64(statement, 2)); + + callback(item); // Invoke caller-supplied callback + } + + sqlite3_finalize(statement); // Finalize the SQLite statement + } + + catch (...) + { + sqlite3_finalize(statement); + throw; + } +} + +//--------------------------------------------------------------------------- +// enumerate_wxradio_channels +// +// Enumerates Weather Radio channels +// +// Arguments: +// +// instance - Database instance +// callback - Callback function + +void enumerate_wxradio_channels(sqlite3* instance, enumerate_channels_callback const& callback) +{ + sqlite3_stmt* statement; // SQL statement to execute + int result; // Result from SQLite function + + if (instance == nullptr) + throw std::invalid_argument("instance"); + + // + // Weather Radio channel numbers are arbitrarily assigned in the range of 401 (WX1) through 407 (WX7) based + // on the content of the (static) namedchannel table + // + + // frequency | channelnumber | subchannelnumber | name | logourl + auto sql = "select channel.frequency as frequency, namedchannel.number as channelnumber, 0 as " + "subchannelnumber, " + "channel.name as name, channel.logourl as logourl from channel inner join " + "namedchannel on channel.frequency = namedchannel.frequency " + "and channel.modulation = namedchannel.modulation where channel.modulation = 3 order " + "by channelnumber, subchannelnumber asc"; + + result = sqlite3_prepare_v2(instance, sql, -1, &statement, nullptr); + if (result != SQLITE_OK) + throw sqlite_exception(result, sqlite3_errmsg(instance)); + + try + { + + // Execute the query and iterate over all returned rows + while (sqlite3_step(statement) == SQLITE_ROW) + { + + struct channel item = {}; + channelid channelid(sqlite3_column_int(statement, 0), modulation::wx); + + item.id = channelid.id(); + item.channel = sqlite3_column_int(statement, 1); + item.subchannel = sqlite3_column_int(statement, 2); + item.name = reinterpret_cast(sqlite3_column_text(statement, 3)); + item.logourl = reinterpret_cast(sqlite3_column_text(statement, 4)); + + callback(item); // Invoke caller-supplied callback + } + + sqlite3_finalize(statement); // Finalize the SQLite statement + } + + catch (...) + { + sqlite3_finalize(statement); + throw; + } +} + +//--------------------------------------------------------------------------- +// execute_non_query (local) +// +// Executes a database query and returns the number of rows affected +// +// Arguments: +// +// instance - Database instance +// sql - SQL query to execute +// parameters - Parameters to be bound to the query + +template +static int execute_non_query(sqlite3* instance, char const* sql, _parameters&&... parameters) +{ + sqlite3_stmt* statement; // SQL statement to execute + int paramindex = 1; // Bound parameter index value + + if (instance == nullptr) + throw std::invalid_argument("instance"); + if (sql == nullptr) + throw std::invalid_argument("sql"); + + // Suppress unreferenced local variable warning when there are no parameters to bind + (void)paramindex; + + // Prepare the statement + int result = sqlite3_prepare_v2(instance, sql, -1, &statement, nullptr); + if (result != SQLITE_OK) + throw sqlite_exception(result, sqlite3_errmsg(instance)); + + try + { + + // Bind the provided query parameter(s) by unpacking the parameter pack + int unpack[] = {0, + (static_cast(bind_parameter(statement, paramindex, parameters)), 0)...}; + (void)unpack; + + // Execute the query; ignore any rows that are returned + do + result = sqlite3_step(statement); + while (result == SQLITE_ROW); + + // The final result from sqlite3_step should be SQLITE_DONE + if (result != SQLITE_DONE) + throw sqlite_exception(result, sqlite3_errmsg(instance)); + + // Finalize the statement + sqlite3_finalize(statement); + + // Return the number of changes made by the statement + return sqlite3_changes(instance); + } + + catch (...) + { + sqlite3_finalize(statement); + throw; + } +} + +//--------------------------------------------------------------------------- +// execute_scalar_int (local) +// +// Executes a database query and returns a scalar integer result +// +// Arguments: +// +// instance - Database instance +// sql - SQL query to execute +// parameters - Parameters to be bound to the query + +template +static int execute_scalar_int(sqlite3* instance, char const* sql, _parameters&&... parameters) +{ + sqlite3_stmt* statement; // SQL statement to execute + int paramindex = 1; // Bound parameter index value + int value = 0; // Result from the scalar function + + if (instance == nullptr) + throw std::invalid_argument("instance"); + if (sql == nullptr) + throw std::invalid_argument("sql"); + + // Suppress unreferenced local variable warning when there are no parameters to bind + (void)paramindex; + + // Prepare the statement + int result = sqlite3_prepare_v2(instance, sql, -1, &statement, nullptr); + if (result != SQLITE_OK) + throw sqlite_exception(result, sqlite3_errmsg(instance)); + + try + { + + // Bind the provided query parameter(s) by unpacking the parameter pack + int unpack[] = {0, + (static_cast(bind_parameter(statement, paramindex, parameters)), 0)...}; + (void)unpack; + + // Execute the query; only the first row returned will be used + result = sqlite3_step(statement); + + if (result == SQLITE_ROW) + value = sqlite3_column_int(statement, 0); + else if (result != SQLITE_DONE) + throw sqlite_exception(result, sqlite3_errmsg(instance)); + + // Finalize the statement + sqlite3_finalize(statement); + + // Return the resultant value from the scalar query + return value; + } + + catch (...) + { + sqlite3_finalize(statement); + throw; + } +} + +//--------------------------------------------------------------------------- +// execute_scalar_string (local) +// +// Executes a database query and returns a scalar string result +// +// Arguments: +// +// instance - Database instance +// sql - SQL query to execute +// parameters - Parameters to be bound to the query + +template +static std::string execute_scalar_string(sqlite3* instance, + char const* sql, + _parameters&&... parameters) +{ + sqlite3_stmt* statement; // SQL statement to execute + int paramindex = 1; // Bound parameter index value + std::string value; // Result from the scalar function + + if (instance == nullptr) + throw std::invalid_argument("instance"); + if (sql == nullptr) + throw std::invalid_argument("sql"); + + // Suppress unreferenced local variable warning when there are no parameters to bind + (void)paramindex; + + // Prepare the statement + int result = sqlite3_prepare_v2(instance, sql, -1, &statement, nullptr); + if (result != SQLITE_OK) + throw sqlite_exception(result, sqlite3_errmsg(instance)); + + try + { + + // Bind the provided query parameter(s) by unpacking the parameter pack + int unpack[] = {0, + (static_cast(bind_parameter(statement, paramindex, parameters)), 0)...}; + (void)unpack; + + // Execute the query; only the first row returned will be used + result = sqlite3_step(statement); + + if (result == SQLITE_ROW) + { + + char const* ptr = reinterpret_cast(sqlite3_column_text(statement, 0)); + if (ptr != nullptr) + value.assign(ptr); + } + else if (result != SQLITE_DONE) + throw sqlite_exception(result, sqlite3_errmsg(instance)); + + // Finalize the statement + sqlite3_finalize(statement); + + // Return the resultant value from the scalar query + return value; + } + + catch (...) + { + sqlite3_finalize(statement); + throw; + } +} + +//--------------------------------------------------------------------------- +// export_channels +// +// Exports the channels into a JSON file +// +// Arguments: +// +// instance - SQLite database instance +// path - Path to the output file to generate + +std::string export_channels(sqlite3* instance) +{ + if (instance == nullptr) + throw std::invalid_argument("instance"); + + return execute_scalar_string( + instance, "select json_group_array(json_object(" + "'frequency', frequency, 'modulation', case modulation when 0 then 'FM' when 1 " + "then 'HD' when 2 then 'DAB' when 3 then 'WX' else 'FM' end, " + "'name', name, 'autogain', autogain, 'manualgain', manualgain, 'freqcorrection', " + "freqcorrection, 'logourl', logourl)) " + "from channel"); +} + +//--------------------------------------------------------------------------- +// get_channel_count +// +// Gets the number of available channels in the database +// +// Arguments: +// +// instance - SQLite database instance + +int get_channel_count(sqlite3* instance) +{ + if (instance == nullptr) + return 0; + + // Use the same LEFT OUTER JOIN logic against subchannel to return an accurate count when a digital + // channel does not have any defined subchannels and will return a .0 channel instance + return execute_scalar_int( + instance, + "select count(*) from channel left outer join subchannel " + "on channel.frequency = subchannel.frequency and channel.modulation = subchannel.modulation"); +} + +//--------------------------------------------------------------------------- +// get_channel_properties +// +// Gets the tuning properties of a channel from the database +// +// Arguments: +// +// instance - SQLite database instance +// frequency - Channel frequency +// modulation - Channel modulation +// id - Channel unique identifier +// channelprops - Structure to receive the channel properties + +bool get_channel_properties(sqlite3* instance, + uint32_t frequency, + enum modulation modulation, + struct channelprops& channelprops) +{ + sqlite3_stmt* statement; // SQL statement to execute + int result; // Result from SQLite function + bool found = false; // Flag if channel was found in database + + if (instance == nullptr) + throw std::invalid_argument("instance"); + + // name | autogain | manualgain | freqcorrection | logourl + auto sql = "select name, autogain, manualgain, freqcorrection, logourl from channel where " + "frequency = ?1 and modulation = ?2"; + + result = sqlite3_prepare_v2(instance, sql, -1, &statement, nullptr); + if (result != SQLITE_OK) + throw sqlite_exception(result, sqlite3_errmsg(instance)); + + try + { + + // Bind the query parameters + result = sqlite3_bind_int64(statement, 1, static_cast(frequency)); + if (result == SQLITE_OK) + sqlite3_bind_int(statement, 2, static_cast(modulation)); + if (result != SQLITE_OK) + throw sqlite_exception(result); + + // Execute the query; there should be one and only one row returned + if (sqlite3_step(statement) == SQLITE_ROW) + { + + channelprops.frequency = frequency; + channelprops.modulation = modulation; + + unsigned char const* name = sqlite3_column_text(statement, 0); + channelprops.name.assign((name == nullptr) ? "" : reinterpret_cast(name)); + + channelprops.autogain = (sqlite3_column_int(statement, 1) != 0); + channelprops.manualgain = sqlite3_column_int(statement, 2); + channelprops.freqcorrection = sqlite3_column_int(statement, 3); + + unsigned char const* logourl = sqlite3_column_text(statement, 4); + channelprops.logourl.assign((logourl == nullptr) ? "" + : reinterpret_cast(logourl)); + + found = true; // Channel was found in the database + } + + sqlite3_finalize(statement); // Finalize the SQLite statement + } + + catch (...) + { + sqlite3_finalize(statement); + throw; + } + + return found; +} + +//--------------------------------------------------------------------------- +// get_channel_properties +// +// Gets the tuning properties of a channel from the database +// +// Arguments: +// +// instance - SQLite database instance +// frequency - Channel frequency +// modulation - Channel modulation +// id - Channel unique identifier +// channelprops - Structure to receive the channel properties +// subchannelprops - vector<> containing subchannel properties + +bool get_channel_properties(sqlite3* instance, + uint32_t frequency, + enum modulation modulation, + struct channelprops& channelprops, + std::vector& subchannelprops) +{ + sqlite3_stmt* statement; // SQL statement to execute + int result; // Result from SQLite function + + if (instance == nullptr) + throw std::invalid_argument("instance"); + + // Get the parent channel properties before enumerating the subchannels + if (!get_channel_properties(instance, frequency, modulation, channelprops)) + return false; + + subchannelprops.clear(); // Reset the vector<> + + // number | name | logourl + auto sql = "select number, name, logourl from subchannel where frequency = ?1 and modulation = " + "?2 order by number"; + + result = sqlite3_prepare_v2(instance, sql, -1, &statement, nullptr); + if (result != SQLITE_OK) + throw sqlite_exception(result, sqlite3_errmsg(instance)); + + try + { + + // Bind the query parameters + result = sqlite3_bind_int64(statement, 1, static_cast(frequency)); + if (result == SQLITE_OK) + sqlite3_bind_int(statement, 2, static_cast(modulation)); + if (result != SQLITE_OK) + throw sqlite_exception(result); + + // Execute the query and iterate over all returned rows + while (sqlite3_step(statement) == SQLITE_ROW) + { + + struct subchannelprops subchannel = {}; + + subchannel.number = sqlite3_column_int(statement, 0); + + unsigned char const* name = sqlite3_column_text(statement, 1); + subchannel.name.assign((name == nullptr) ? "" : reinterpret_cast(name)); + + unsigned char const* logourl = sqlite3_column_text(statement, 2); + subchannel.logourl.assign((logourl == nullptr) ? "" : reinterpret_cast(logourl)); + + subchannelprops.emplace_back(std::move(subchannel)); + } + + sqlite3_finalize(statement); // Finalize the SQLite statement + } + + catch (...) + { + sqlite3_finalize(statement); + throw; + } + + return true; +} + +//--------------------------------------------------------------------------- +// has_rawfiles +// +// Gets a flag indicating if there are raw input files available to use +// +// Arguments: +// +// instance - Database instance + +bool has_rawfiles(sqlite3* instance) +{ + if (instance == nullptr) + throw std::invalid_argument("instance"); + + return execute_scalar_int(instance, "select exists(select path from rawfile)") != 0; +} + +//--------------------------------------------------------------------------- +// import_channels +// +// Imports channels from a JSON string +// +// Arguments: +// +// instance - Database instance +// json - JSON data to be imported + +void import_channels(sqlite3* instance, char const* json) +{ + if (instance == nullptr) + throw std::invalid_argument("instance"); + if ((json == nullptr) || (*json == '\0')) + throw std::invalid_argument("instance"); + + // + // TODO: Add a 'channel' element that can replace frequency/modulation for named channels. + // The JSON will likely need to be put into a temp table anyway to deal with subchannels + // so this entire operation will likely need to be redone regardless + // + + // Massage the input as much as possible, only the frequency is actually required, + // the rest can be defaulted if not present. Also watch out for duplicates and the frequency range + execute_non_query( + instance, + "replace into channel " + "select cast(json_extract(entry.value, '$.frequency') as integer) as frequency, " + "case upper(cast(ifnull(json_extract(entry.value, '$.modulation'), '') as text)) " + " when 'FM' then 0 " + " when 'FMRADIO' then 0 " + " when 'HD' then 1 " + " when 'HDRADIO' then 1 " + " when 'DAB' then 2 " + " when 'DAB+' then 2 " + " when 'WX' then 3 " + " when 'WEATHER' then 3 " + " else case " + " when cast(json_extract(entry.value, '$.frequency') as integer) between 174928000 and " + "239200000 then 2 " // DAB + " when cast(json_extract(entry.value, '$.frequency') as integer) between 162400000 and " + "162550000 then 3 " // WX + " else 0 end " // FM + " end as modulation, " + "cast(ifnull(json_extract(entry.value, '$.name'), '') as text) as name, " + "cast(ifnull(json_extract(entry.value, '$.autogain'), 0) as integer) as autogain, " + "cast(ifnull(json_extract(entry.value, '$.manualgain'), 0) as integer) as manualgain, " + "cast(ifnull(json_extract(entry.value, '$.freqcorrection'), 0) as integer) as " + "freqcorrection, " + "json_extract(entry.value, '$.logourl') as logourl " // <-- this one allows nulls + "from json_each(?1) as entry " + "where frequency is not null and " + " ((frequency between 87500000 and 108000000) or " // FM / HD + " (frequency between 174928000 and 239200000) or " // DAB + " (frequency between 162400000 and 162550000)) " // WX + " and modulation between 0 and 3 " + "group by frequency, modulation", + json); + + // Remove any FM channels that are outside the frequency range + execute_non_query(instance, "delete from channel where modulation = 0 and " + "frequency not between 87500000 and 108000000"); + + // Remove any HD Radio channels that are outside the frequency range + execute_non_query( + instance, "delete from channel where modulation = 0 and " + "(frequency not between 87900000 and 107900000 or (frequency / 100000) % 2 = 0)"); + + // Remove any DAB channels that don't match an entry in namedchannel + execute_non_query(instance, + "delete from channel where modulation = 2 and " + "frequency not in(select frequency from namedchannel where modulation = 2)"); + + // Remove any Weather Radio channels that don't match an entry in namedchannel + execute_non_query(instance, + "delete from channel where modulation = 3 and " + "frequency not in(select frequency from namedchannel where modulation = 3)"); +} + +//--------------------------------------------------------------------------- +// open_database +// +// Opens the SQLite database instance +// +// Arguments: +// +// connstring - Database connection string +// flags - Database open flags (see sqlite3_open_v2) + +sqlite3* open_database(char const* connstring, int flags) +{ + return open_database(connstring, flags, false); +} + +//--------------------------------------------------------------------------- +// open_database +// +// Opens the SQLite database instance +// +// Arguments: +// +// connstring - Database connection string +// flags - Database open flags (see sqlite3_open_v2) +// initialize - Flag indicating database schema should be (re)initialized + +sqlite3* open_database(char const* connstring, int flags, bool initialize) +{ + sqlite3* instance = nullptr; // SQLite database instance + + // Create the database using the provided connection string + int result = sqlite3_open_v2(connstring, &instance, flags, nullptr); + if (result != SQLITE_OK) + throw sqlite_exception(result); + + // set the connection to report extended error codes + sqlite3_extended_result_codes(instance, -1); + + // set a busy_timeout handler for this connection + sqlite3_busy_timeout(instance, 5000); + + try + { + + // switch the database to write-ahead logging + // + execute_non_query(instance, "pragma journal_mode=wal"); + + // Only execute schema creation steps if the database is being initialized; the caller needs + // to ensure that this is set for only one connection otherwise locking issues can occur + if (initialize) + { + + // get the database schema version + // + int dbversion = execute_scalar_int(instance, "pragma user_version"); + + // SCHEMA VERSION 0 -> VERSION 1 + // + if (dbversion == 0) + { + + // table: channel + // + // frequency(pk) | subchannel(pk) | hidden | name | autogain | manualgain | freqcorrection | logourl + execute_non_query(instance, "drop table if exists channel"); + execute_non_query( + instance, + "create table channel(frequency integer not null, subchannel integer not null, " + "hidden integer not null, name text not null, autogain integer not null, manualgain " + "integer not null, freqcorrection integer not null, " + "logourl text null, primary key(frequency, subchannel))"); + + execute_non_query(instance, "pragma user_version = 1"); + dbversion = 1; + } + + // SCHEMA VERSION 1 -> VERSION 2 + // + if (dbversion == 1) + { + + // table: channel_v1 + // + // frequency(pk) | subchannel(pk) | hidden | name | autogain | manualgain | logourl + execute_non_query(instance, "alter table channel rename to channel_v1"); + + // table: channel + // + // frequency(pk) | subchannel(pk) | modulation (pk) | hidden | name | autogain | manualgain | freqcorrection | logourl + execute_non_query(instance, + "create table channel(frequency integer not null, subchannel integer not " + "null, modulation integer not null, " + "hidden integer not null, name text not null, autogain integer not null, " + "manualgain integer not null, freqcorrection integer not null, " + "logourl text null, primary key(frequency, subchannel, modulation))"); + + // Version 1 modulation can be gleaned from the frequency, anything between 162.400MHz and 162.550MHz is WX (3), anything else is FM (0) + execute_non_query( + instance, + "insert into channel select v1.frequency, v1.subchannel, case when (v1.frequency >= " + "162400000 and v1.frequency <= 162550000) then 3 else 0 end, " + "v1.hidden, v1.name, v1.autogain, v1.manualgain, 0, v1.logourl from channel_v1 as v1"); + + execute_non_query(instance, "drop table channel_v1"); + execute_non_query(instance, "pragma user_version = 2"); + dbversion = 2; + } + + // SCHEMA VERSION 2 -> VERSION 3 + // + if (dbversion == 2) + { + + // table: channel_v2 + // + // frequency(pk) | subchannel(pk) | modulation (pk) | hidden | name | autogain | manualgain | freqcorrection | logourl + execute_non_query(instance, "alter table channel rename to channel_v2"); + + // table: channel + // + // frequency(pk) | modulation (pk) | name | autogain | manualgain | freqcorrection | logourl + execute_non_query( + instance, + "create table channel(frequency integer not null, modulation integer not null, " + "name text not null, autogain integer not null, manualgain integer not null, " + "freqcorrection integer not null, " + "logourl text null, primary key(frequency, modulation))"); + + // There shouldn't be any channels with a subchannel number in the previous version of the table, but clean them + // out during the upgrade just in case to avoid possible primary key violation on the reload + execute_non_query(instance, "delete from channel_v2 where subchannel != 0"); + execute_non_query(instance, "insert into channel select v2.frequency, v2.modulation, " + "v2.name, v2.autogain, v2.manualgain, " + "v2.freqcorrection, v2.logourl from channel_v2 as v2"); + + // table: subchannel + // + // frequency(pk) | subchannel(pk) | modulation (pk) | name + execute_non_query(instance, "drop table if exists subchannel"); + execute_non_query( + instance, + "create table subchannel(frequency integer not null, number integer not null, " + "modulation integer not null, " + "name text not null, logourl null, primary key(frequency, number, modulation))"); + + execute_non_query(instance, "drop table channel_v2"); + + // table: rawfile + // + // path(pk) | name | samplerate + execute_non_query(instance, "drop table if exists rawfile"); + execute_non_query(instance, "create table rawfile(path text not null, name text not null, " + "samplerate integer not null, primary key(path))"); + + // table: namedchannel + // + // frequency(pk) | modulation(pk) | name | number + execute_non_query(instance, "drop table if exists namedchannel"); + execute_non_query( + instance, + "create table namedchannel(frequency integer not null, modulation integer not null, " + "name text not null, number not null, primary key(frequency, modulation))"); + + // DAB Band III + // + execute_non_query(instance, "insert into namedchannel values(174928000, 2, '5A', 301)"); + execute_non_query(instance, "insert into namedchannel values(176640000, 2, '5B', 302)"); + execute_non_query(instance, "insert into namedchannel values(178352000, 2, '5C', 303)"); + execute_non_query(instance, "insert into namedchannel values(180064000, 2, '5D', 304)"); + execute_non_query(instance, "insert into namedchannel values(181936000, 2, '6A', 305)"); + execute_non_query(instance, "insert into namedchannel values(183648000, 2, '6B', 306)"); + execute_non_query(instance, "insert into namedchannel values(185360000, 2, '6C', 307)"); + execute_non_query(instance, "insert into namedchannel values(187072000, 2, '6D', 308)"); + execute_non_query(instance, "insert into namedchannel values(188928000, 2, '7A', 309)"); + execute_non_query(instance, "insert into namedchannel values(190640000, 2, '7B', 310)"); + execute_non_query(instance, "insert into namedchannel values(192352000, 2, '7C', 311)"); + execute_non_query(instance, "insert into namedchannel values(194064000, 2, '7D', 312)"); + execute_non_query(instance, "insert into namedchannel values(195936000, 2, '8A', 313)"); + execute_non_query(instance, "insert into namedchannel values(197648000, 2, '8B', 314)"); + execute_non_query(instance, "insert into namedchannel values(199360000, 2, '8C', 315)"); + execute_non_query(instance, "insert into namedchannel values(201072000, 2, '8D', 316)"); + execute_non_query(instance, "insert into namedchannel values(202928000, 2, '9A', 317)"); + execute_non_query(instance, "insert into namedchannel values(204640000, 2, '9B', 318)"); + execute_non_query(instance, "insert into namedchannel values(206352000, 2, '9C', 319)"); + execute_non_query(instance, "insert into namedchannel values(208064000, 2, '9D', 320)"); + execute_non_query(instance, "insert into namedchannel values(209936000, 2, '10A', 321)"); + execute_non_query(instance, "insert into namedchannel values(211648000, 2, '10B', 322)"); + execute_non_query(instance, "insert into namedchannel values(213360000, 2, '10C', 323)"); + execute_non_query(instance, "insert into namedchannel values(215072000, 2, '10D', 324)"); + execute_non_query(instance, "insert into namedchannel values(216928000, 2, '11A', 325)"); + execute_non_query(instance, "insert into namedchannel values(218640000, 2, '11B', 326)"); + execute_non_query(instance, "insert into namedchannel values(220352000, 2, '11C', 327)"); + execute_non_query(instance, "insert into namedchannel values(222064000, 2, '11D', 328)"); + execute_non_query(instance, "insert into namedchannel values(223936000, 2, '12A', 329)"); + execute_non_query(instance, "insert into namedchannel values(225648000, 2, '12B', 330)"); + execute_non_query(instance, "insert into namedchannel values(227360000, 2, '12C', 331)"); + execute_non_query(instance, "insert into namedchannel values(229072000, 2, '12D', 332)"); + execute_non_query(instance, "insert into namedchannel values(230784000, 2, '13A', 333)"); + execute_non_query(instance, "insert into namedchannel values(232496000, 2, '13B', 334)"); + execute_non_query(instance, "insert into namedchannel values(234208000, 2, '13C', 335)"); + execute_non_query(instance, "insert into namedchannel values(235776000, 2, '13D', 336)"); + execute_non_query(instance, "insert into namedchannel values(237488000, 2, '13E', 337)"); + execute_non_query(instance, "insert into namedchannel values(239200000, 2, '13F', 338)"); + + // Weather Radio + // + execute_non_query(instance, "insert into namedchannel values(162400000, 3, 'WX2', 402)"); + execute_non_query(instance, "insert into namedchannel values(162425000, 3, 'WX4', 404)"); + execute_non_query(instance, "insert into namedchannel values(162450000, 3, 'WX5', 405)"); + execute_non_query(instance, "insert into namedchannel values(162475000, 3, 'WX3', 403)"); + execute_non_query(instance, "insert into namedchannel values(162500000, 3, 'WX6', 406)"); + execute_non_query(instance, "insert into namedchannel values(162525000, 3, 'WX7', 407)"); + execute_non_query(instance, "insert into namedchannel values(162550000, 3, 'WX1', 401)"); + + // Remove any Weather Radio channels that don't match an entry in namedchannel + execute_non_query( + instance, "delete from channel where modulation = 3 and " + "frequency not in(select frequency from namedchannel where modulation = 3)"); + + execute_non_query(instance, "pragma user_version = 3"); + dbversion = 3; + } + } + } + + // Close the database instance on any thrown exceptions + catch (...) + { + sqlite3_close(instance); + throw; + } + + return instance; +} + +//--------------------------------------------------------------------------- +// rename_channel +// +// Renames a channel in the database +// +// Arguments: +// +// instance - Database instance +// frequency - Frequency of the channel to be renamed +// modulation - Modulation of the channel to be renamed +// newname - New name to assign to the channel + +void rename_channel(sqlite3* instance, + uint32_t frequency, + enum modulation modulation, + char const* newname) +{ + if (instance == nullptr) + throw std::invalid_argument("instance"); + + execute_non_query(instance, + "update channel set name = ?1 where frequency = ?2 and modulation = ?3", + (newname == nullptr) ? "" : newname, frequency, static_cast(modulation)); +} + +//--------------------------------------------------------------------------- +// try_execute_non_query +// +// Executes a non-query against the database and eats any exceptions +// +// Arguments: +// +// instance - Database instance +// sql - SQL non-query to execute + +bool try_execute_non_query(sqlite3* instance, char const* sql) +{ + try + { + execute_non_query(instance, sql); + } + catch (...) + { + return false; + } + + return true; +} + +//--------------------------------------------------------------------------- +// update_channel +// +// Updates the tuning properties of a channel in the database +// +// Arguments: +// +// instance - SQLite database instance +// channelprops - Structure containing the updated channel properties + +bool update_channel(sqlite3* instance, struct channelprops const& channelprops) +{ + if (instance == nullptr) + throw std::invalid_argument("instance"); + + return execute_non_query(instance, + "update channel set name = ?1, autogain = ?2, manualgain = ?3, " + "freqcorrection = ?4, logourl = ?5 " + "where frequency = ?6 and modulation = ?7", + channelprops.name.c_str(), (channelprops.autogain) ? 1 : 0, + channelprops.manualgain, channelprops.freqcorrection, + channelprops.logourl.c_str(), channelprops.frequency, + static_cast(channelprops.modulation)) > 0; +} + +//--------------------------------------------------------------------------- +// update_channel +// +// Updates the tuning properties of a channel in the database +// +// Arguments: +// +// instance - SQLite database instance +// channelprops - Structure containing the updated channel properties +// subchannelprops - vector<> containing updated subchannel properties + +bool update_channel(sqlite3* instance, + struct channelprops const& channelprops, + std::vector const& subchannelprops) +{ + bool result = false; // Result code to return to caller + + try + { + + // Clone the subchannel table schema into a temporary table + execute_non_query(instance, "drop table if exists subchannel_temp"); + execute_non_query(instance, + "create temp table subchannel_temp as select * from subchannel limit 0"); + + // frequency | subchannel | modulation | name | logourl + for (auto const& it : subchannelprops) + { + + execute_non_query(instance, "insert into subchannel_temp values(?1, ?2, ?3, ?4, ?5)", + channelprops.frequency, it.number, + static_cast(channelprops.modulation), it.name.c_str(), + channelprops.logourl.c_str()); + } + + // This requires a multi-step operation; start a transaction + execute_non_query(instance, "begin immediate transaction"); + + try + { + + // Update the base channel properties + result = execute_non_query(instance, + "update channel set name = ?1, autogain = ?2, manualgain = ?3, " + "freqcorrection = ?4, logourl = ?5 " + "where frequency = ?6 and modulation = ?7", + channelprops.name.c_str(), (channelprops.autogain) ? 1 : 0, + channelprops.manualgain, channelprops.freqcorrection, + channelprops.logourl.c_str(), channelprops.frequency, + static_cast(channelprops.modulation)) > 0; + + if (result) + { + + // Remove subchannels that no longer exist for this channel + execute_non_query(instance, + "delete from subchannel where frequency = ?1 and modulation = ?2 " + "and number not in (select number from subchannel_temp)", + channelprops.frequency, static_cast(channelprops.modulation)); + + // Use an upsert operation to insert/update the remaining subchannels, only replace the name if necessary + execute_non_query( + instance, + "insert into subchannel select * from subchannel_temp where true " + "on conflict(frequency, modulation, number) do update set name=excluded.name"); + } + + // Commit the database transaction + execute_non_query(instance, "commit transaction"); + } + + // Rollback the transaction on any exception + catch (...) + { + try_execute_non_query(instance, "rollback transaction"); + throw; + } + + // Drop the temporary table + execute_non_query(instance, "drop table subchannel_temp"); + } + + // Drop the temporary table on any exception + catch (...) + { + execute_non_query(instance, "drop table subchannel_temp"); + throw; + } + + return result; +} + +//--------------------------------------------------------------------------- + +#pragma warning(pop) diff --git a/src/filedevice.cpp b/src/filedevice.cpp index 3b82c6f..ab22c6a 100644 --- a/src/filedevice.cpp +++ b/src/filedevice.cpp @@ -1,315 +1,315 @@ -//--------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//--------------------------------------------------------------------------- - -#include "filedevice.h" - -#include "string_exception.h" - -#include -#include -#include -#include -#include -#include - -#pragma warning(push, 4) - -//--------------------------------------------------------------------------- -// filedevice Constructor (private) -// -// Arguments: -// -// filename - Target file name -// samplerate - Target file sample rate - -filedevice::filedevice(char const* filename, uint32_t samplerate) : m_samplerate(samplerate) -{ - if (filename == nullptr) - throw std::invalid_argument("filename"); - - // Canonicalize the path to avoid path traversal vulnerability -#ifdef _WINDOWS - char buf[_MAX_PATH] = {}; - if (_fullpath(buf, filename, _MAX_PATH) != nullptr) - m_filename.assign(buf); - else - throw std::invalid_argument("filename"); -#else - char buf[PATH_MAX] = {}; - if (realpath(filename, buf) != nullptr) - m_filename.assign(buf); - else - throw std::invalid_argument("filename"); -#endif - - // Attempt to open the target file in read-only binary mode - m_file = fopen(m_filename.c_str(), "rb"); - if (m_file == nullptr) - throw string_exception(__func__, ": fopen() failed"); -} - -//--------------------------------------------------------------------------- -// filedevice Destructor - -filedevice::~filedevice() -{ - if (m_file) - fclose(m_file); - m_file = nullptr; -} - -//--------------------------------------------------------------------------- -// filedevice::begin_stream -// -// Starts streaming data from the device -// -// Arguments: -// -// NONE - -void filedevice::begin_stream(void) const -{ -} - -//--------------------------------------------------------------------------- -// filedevice::cancel_async -// -// Cancels any pending asynchronous read operations from the device -// -// Arguments: -// -// NONE - -void filedevice::cancel_async(void) const -{ - // If the asynchronous operation is stopped, do nothing - if (m_stopped.test(true) == true) - return; - - m_stop = true; // Flag a stop condition - m_stopped.wait_until_equals(true); // Wait for async to stop -} - -//--------------------------------------------------------------------------- -// filedevice::create (static) -// -// Factory method, creates a new filedevice instance -// -// Arguments: -// -// filename - Target file name -// samplerate - Target file sample rate - -std::unique_ptr filedevice::create(char const* filename, uint32_t samplerate) -{ - return std::unique_ptr(new filedevice(filename, samplerate)); -} - -//--------------------------------------------------------------------------- -// filedevice::get_device_name -// -// Gets the name of the device -// -// Arguments: -// -// NONE - -char const* filedevice::get_device_name(void) const -{ - return m_filename.c_str(); -} - -//--------------------------------------------------------------------------- -// filedevice::get_valid_gains -// -// Gets the valid tuner gain values for the device -// -// Arguments: -// -// dbs - vector<> to retrieve the valid gain values - -void filedevice::get_valid_gains(std::vector& /*dbs*/) const -{ -} - -//--------------------------------------------------------------------------- -// filedevice::read -// -// Reads data from the device -// -// Arguments: -// -// buffer - Buffer to receive the data -// count - Size of the destination buffer, specified in bytes - -size_t filedevice::read(uint8_t* buffer, size_t count) const -{ - assert(m_file != nullptr); - assert(m_samplerate != 0); - - auto start = std::chrono::steady_clock::now(); - - // Synchronously read the requested amount of data from the input file - size_t read = fread(buffer, sizeof(uint8_t), count, m_file); - - if (read > 0) - { - - // Determine how long this operation should take to execute to maintain sample rate - int duration = static_cast(static_cast(read) / ((m_samplerate * 2) / 1000000.0)); - auto end = start + std::chrono::microseconds(duration); - - // Yield until the calculated duration has expired - while (std::chrono::steady_clock::now() < end) - { - std::this_thread::yield(); - } - } - - return read; -} - -//--------------------------------------------------------------------------- -// filedevice::read_async -// -// Asynchronously reads data from the device -// -// Arguments: -// -// callback - Asynchronous read callback function -// bufferlength - Output buffer length in bytes - -void filedevice::read_async(rtldevice::asynccallback const& callback, uint32_t bufferlength) const -{ - std::unique_ptr buffer(new uint8_t[bufferlength]); // Input data buffer - - m_stop = false; - m_stopped = false; - - try - { - - // Continuously read data from the device until the stop condition is set - while (m_stop.test(true) == false) - { - - // Try to read enough data to fill the input buffer - size_t cb = read(&buffer[0], bufferlength); - callback(&buffer[0], cb); - } - - m_stopped = true; // Operation has been stopped - } - - // Ensure that the stopped condition is set on an exception - catch (...) - { - m_stopped = true; - throw; - } -} - -//--------------------------------------------------------------------------- -// filedevice::set_automatic_gain_control -// -// Enables/disables the automatic gain control mode of the device -// -// Arguments: -// -// enable - Flag to enable/disable test mode - -void filedevice::set_automatic_gain_control(bool /*enable*/) const -{ -} - -//--------------------------------------------------------------------------- -// filedevice::set_center_frequency -// -// Sets the center frequency of the device -// -// Arguments: -// -// hz - Frequency to set, specified in hertz - -uint32_t filedevice::set_center_frequency(uint32_t hz) const -{ - return hz; -} - -//--------------------------------------------------------------------------- -// filedevice::set_frequency_correction -// -// Sets the frequency correction of the device -// -// Arguments: -// -// ppm - Frequency correction to set, specified in parts per million - -int filedevice::set_frequency_correction(int ppm) const -{ - return ppm; -} - -//--------------------------------------------------------------------------- -// filedevice::set_gain -// -// Sets the gain of the device -// -// Arguments: -// -// db - Gain to set, specified in tenths of a decibel - -int filedevice::set_gain(int db) const -{ - return db; -} - -//--------------------------------------------------------------------------- -// filedevice::set_sample_rate -// -// Sets the sample rate of the device -// -// Arguments: -// -// hz - Sample rate to set, specified in hertz - -uint32_t filedevice::set_sample_rate(uint32_t hz) const -{ - return hz; -} - -//--------------------------------------------------------------------------- -// filedevice::set_test_mode -// -// Enables/disables the test mode of the device -// -// Arguments: -// -// enable - Flag to enable/disable test mode - -void filedevice::set_test_mode(bool /*enable*/) const -{ -} - -//--------------------------------------------------------------------------- - -#pragma warning(pop) +//--------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//--------------------------------------------------------------------------- + +#include "filedevice.h" + +#include "string_exception.h" + +#include +#include +#include +#include +#include +#include + +#pragma warning(push, 4) + +//--------------------------------------------------------------------------- +// filedevice Constructor (private) +// +// Arguments: +// +// filename - Target file name +// samplerate - Target file sample rate + +filedevice::filedevice(char const* filename, uint32_t samplerate) : m_samplerate(samplerate) +{ + if (filename == nullptr) + throw std::invalid_argument("filename"); + + // Canonicalize the path to avoid path traversal vulnerability +#ifdef _WINDOWS + char buf[_MAX_PATH] = {}; + if (_fullpath(buf, filename, _MAX_PATH) != nullptr) + m_filename.assign(buf); + else + throw std::invalid_argument("filename"); +#else + char buf[PATH_MAX] = {}; + if (realpath(filename, buf) != nullptr) + m_filename.assign(buf); + else + throw std::invalid_argument("filename"); +#endif + + // Attempt to open the target file in read-only binary mode + m_file = fopen(m_filename.c_str(), "rb"); + if (m_file == nullptr) + throw string_exception(__func__, ": fopen() failed"); +} + +//--------------------------------------------------------------------------- +// filedevice Destructor + +filedevice::~filedevice() +{ + if (m_file) + fclose(m_file); + m_file = nullptr; +} + +//--------------------------------------------------------------------------- +// filedevice::begin_stream +// +// Starts streaming data from the device +// +// Arguments: +// +// NONE + +void filedevice::begin_stream(void) const +{ +} + +//--------------------------------------------------------------------------- +// filedevice::cancel_async +// +// Cancels any pending asynchronous read operations from the device +// +// Arguments: +// +// NONE + +void filedevice::cancel_async(void) const +{ + // If the asynchronous operation is stopped, do nothing + if (m_stopped.test(true) == true) + return; + + m_stop = true; // Flag a stop condition + m_stopped.wait_until_equals(true); // Wait for async to stop +} + +//--------------------------------------------------------------------------- +// filedevice::create (static) +// +// Factory method, creates a new filedevice instance +// +// Arguments: +// +// filename - Target file name +// samplerate - Target file sample rate + +std::unique_ptr filedevice::create(char const* filename, uint32_t samplerate) +{ + return std::unique_ptr(new filedevice(filename, samplerate)); +} + +//--------------------------------------------------------------------------- +// filedevice::get_device_name +// +// Gets the name of the device +// +// Arguments: +// +// NONE + +char const* filedevice::get_device_name(void) const +{ + return m_filename.c_str(); +} + +//--------------------------------------------------------------------------- +// filedevice::get_valid_gains +// +// Gets the valid tuner gain values for the device +// +// Arguments: +// +// dbs - vector<> to retrieve the valid gain values + +void filedevice::get_valid_gains(std::vector& /*dbs*/) const +{ +} + +//--------------------------------------------------------------------------- +// filedevice::read +// +// Reads data from the device +// +// Arguments: +// +// buffer - Buffer to receive the data +// count - Size of the destination buffer, specified in bytes + +size_t filedevice::read(uint8_t* buffer, size_t count) const +{ + assert(m_file != nullptr); + assert(m_samplerate != 0); + + auto start = std::chrono::steady_clock::now(); + + // Synchronously read the requested amount of data from the input file + size_t read = fread(buffer, sizeof(uint8_t), count, m_file); + + if (read > 0) + { + + // Determine how long this operation should take to execute to maintain sample rate + int duration = static_cast(static_cast(read) / ((m_samplerate * 2) / 1000000.0)); + auto end = start + std::chrono::microseconds(duration); + + // Yield until the calculated duration has expired + while (std::chrono::steady_clock::now() < end) + { + std::this_thread::yield(); + } + } + + return read; +} + +//--------------------------------------------------------------------------- +// filedevice::read_async +// +// Asynchronously reads data from the device +// +// Arguments: +// +// callback - Asynchronous read callback function +// bufferlength - Output buffer length in bytes + +void filedevice::read_async(rtldevice::asynccallback const& callback, uint32_t bufferlength) const +{ + std::unique_ptr buffer(new uint8_t[bufferlength]); // Input data buffer + + m_stop = false; + m_stopped = false; + + try + { + + // Continuously read data from the device until the stop condition is set + while (m_stop.test(true) == false) + { + + // Try to read enough data to fill the input buffer + size_t cb = read(&buffer[0], bufferlength); + callback(&buffer[0], cb); + } + + m_stopped = true; // Operation has been stopped + } + + // Ensure that the stopped condition is set on an exception + catch (...) + { + m_stopped = true; + throw; + } +} + +//--------------------------------------------------------------------------- +// filedevice::set_automatic_gain_control +// +// Enables/disables the automatic gain control mode of the device +// +// Arguments: +// +// enable - Flag to enable/disable test mode + +void filedevice::set_automatic_gain_control(bool /*enable*/) const +{ +} + +//--------------------------------------------------------------------------- +// filedevice::set_center_frequency +// +// Sets the center frequency of the device +// +// Arguments: +// +// hz - Frequency to set, specified in hertz + +uint32_t filedevice::set_center_frequency(uint32_t hz) const +{ + return hz; +} + +//--------------------------------------------------------------------------- +// filedevice::set_frequency_correction +// +// Sets the frequency correction of the device +// +// Arguments: +// +// ppm - Frequency correction to set, specified in parts per million + +int filedevice::set_frequency_correction(int ppm) const +{ + return ppm; +} + +//--------------------------------------------------------------------------- +// filedevice::set_gain +// +// Sets the gain of the device +// +// Arguments: +// +// db - Gain to set, specified in tenths of a decibel + +int filedevice::set_gain(int db) const +{ + return db; +} + +//--------------------------------------------------------------------------- +// filedevice::set_sample_rate +// +// Sets the sample rate of the device +// +// Arguments: +// +// hz - Sample rate to set, specified in hertz + +uint32_t filedevice::set_sample_rate(uint32_t hz) const +{ + return hz; +} + +//--------------------------------------------------------------------------- +// filedevice::set_test_mode +// +// Enables/disables the test mode of the device +// +// Arguments: +// +// enable - Flag to enable/disable test mode + +void filedevice::set_test_mode(bool /*enable*/) const +{ +} + +//--------------------------------------------------------------------------- + +#pragma warning(pop) diff --git a/src/filedevice.h b/src/filedevice.h index 03e35d4..4482798 100644 --- a/src/filedevice.h +++ b/src/filedevice.h @@ -1,144 +1,144 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//----------------------------------------------------------------------------- - -#ifndef __FILEDEVICE_H_ -#define __FILEDEVICE_H_ -#pragma once - -#include "rtldevice.h" -#include "scalar_condition.h" - -#include -#include -#include - -#pragma warning(push, 4) - -//--------------------------------------------------------------------------- -// Class filedevice -// -// Implements a dummy device that reads the I/Q samples from a file. This is -// only intended for debugging purposes as there is no control over the -// parameters like frequency, sample rate, etc; those will have been set at -// the time when the file was captured - -class filedevice : public rtldevice -{ -public: - // Destructor - // - virtual ~filedevice(); - - //----------------------------------------------------------------------- - // Member Functions - - // begin_stream - // - // Starts streaming data from the device - void begin_stream(void) const override; - - // cancel_async - // - // Cancels any pending asynchronous read operations from the device - void cancel_async(void) const override; - - // create (static) - // - // Factory method, creates a new filedevice instance - static std::unique_ptr create(char const* filename, uint32_t samplerate); - - // get_device_name - // - // Gets the name of the device - char const* get_device_name(void) const override; - - // get_valid_gains - // - // Gets the valid tuner gain values for the device - void get_valid_gains(std::vector& dbs) const override; - - // read - // - // Reads data from the device - size_t read(uint8_t* buffer, size_t count) const override; - - // read_async - // - // Asynchronously reads data from the device - void read_async(rtldevice::asynccallback const& callback, uint32_t bufferlength) const override; - - // set_automatic_gain_control - // - // Enables/disables the automatic gain control of the device - void set_automatic_gain_control(bool enable) const override; - - // set_center_frequency - // - // Sets the center frequency of the device - uint32_t set_center_frequency(uint32_t hz) const override; - - // set_frequency_correction - // - // Sets the frequency correction of the device - int set_frequency_correction(int ppm) const override; - - // set_gain - // - // Sets the gain value of the device - int set_gain(int db) const override; - - // set_sample_rate - // - // Sets the sample rate of the device - uint32_t set_sample_rate(uint32_t hz) const override; - - // set_test_mode - // - // Enables/disables the test mode of the device - void set_test_mode(bool enable) const override; - -private: - filedevice(filedevice const&) = delete; - filedevice& operator=(filedevice const&) = delete; - - // Instance Constructor - // - filedevice(char const* filename, uint32_t samplerate); - - //----------------------------------------------------------------------- - // Member Variables - - std::string m_filename; // File name - uint32_t const m_samplerate; // Sample rate - FILE* m_file = nullptr; // File handle - - // ASYNCHRONOUS SUPPORT - // - mutable scalar_condition m_stop{false}; // Flag to stop async - mutable scalar_condition m_stopped{true}; // Async stopped condition -}; - -//----------------------------------------------------------------------------- - -#pragma warning(pop) - -#endif // __FILEDEVICE_H_ +//----------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef __FILEDEVICE_H_ +#define __FILEDEVICE_H_ +#pragma once + +#include "rtldevice.h" +#include "scalar_condition.h" + +#include +#include +#include + +#pragma warning(push, 4) + +//--------------------------------------------------------------------------- +// Class filedevice +// +// Implements a dummy device that reads the I/Q samples from a file. This is +// only intended for debugging purposes as there is no control over the +// parameters like frequency, sample rate, etc; those will have been set at +// the time when the file was captured + +class filedevice : public rtldevice +{ +public: + // Destructor + // + virtual ~filedevice(); + + //----------------------------------------------------------------------- + // Member Functions + + // begin_stream + // + // Starts streaming data from the device + void begin_stream(void) const override; + + // cancel_async + // + // Cancels any pending asynchronous read operations from the device + void cancel_async(void) const override; + + // create (static) + // + // Factory method, creates a new filedevice instance + static std::unique_ptr create(char const* filename, uint32_t samplerate); + + // get_device_name + // + // Gets the name of the device + char const* get_device_name(void) const override; + + // get_valid_gains + // + // Gets the valid tuner gain values for the device + void get_valid_gains(std::vector& dbs) const override; + + // read + // + // Reads data from the device + size_t read(uint8_t* buffer, size_t count) const override; + + // read_async + // + // Asynchronously reads data from the device + void read_async(rtldevice::asynccallback const& callback, uint32_t bufferlength) const override; + + // set_automatic_gain_control + // + // Enables/disables the automatic gain control of the device + void set_automatic_gain_control(bool enable) const override; + + // set_center_frequency + // + // Sets the center frequency of the device + uint32_t set_center_frequency(uint32_t hz) const override; + + // set_frequency_correction + // + // Sets the frequency correction of the device + int set_frequency_correction(int ppm) const override; + + // set_gain + // + // Sets the gain value of the device + int set_gain(int db) const override; + + // set_sample_rate + // + // Sets the sample rate of the device + uint32_t set_sample_rate(uint32_t hz) const override; + + // set_test_mode + // + // Enables/disables the test mode of the device + void set_test_mode(bool enable) const override; + +private: + filedevice(filedevice const&) = delete; + filedevice& operator=(filedevice const&) = delete; + + // Instance Constructor + // + filedevice(char const* filename, uint32_t samplerate); + + //----------------------------------------------------------------------- + // Member Variables + + std::string m_filename; // File name + uint32_t const m_samplerate; // Sample rate + FILE* m_file = nullptr; // File handle + + // ASYNCHRONOUS SUPPORT + // + mutable scalar_condition m_stop{false}; // Flag to stop async + mutable scalar_condition m_stopped{true}; // Async stopped condition +}; + +//----------------------------------------------------------------------------- + +#pragma warning(pop) + +#endif // __FILEDEVICE_H_ diff --git a/src/fmdsp/datatypes.h b/src/fmdsp/datatypes.h index d9aabb2..d1074a2 100644 --- a/src/fmdsp/datatypes.h +++ b/src/fmdsp/datatypes.h @@ -1,144 +1,144 @@ -////////////////////////////////////////////////////////////////////// -// datatypes.h: Common data type declarations -// -// History: -// 2010-09-15 Initial creation MSW -// 2011-03-27 Initial release -// 2013-07-28 Added single/double precision math macros -////////////////////////////////////////////////////////////////////// -#ifndef DATATYPES_H -#define DATATYPES_H - -#include -#include - -// uncomment to use double precision math -// #define FMDSP_USE_DOUBLE_PRECISION - -// uncomment to enable thread safety mechanisms -// #define FMDSP_THREAD_SAFE - -// Qt compatibility -// -typedef int8_t qint8; -typedef int16_t qint16; -typedef int32_t qint32; -typedef uint8_t quint8; -typedef uint16_t quint16; -typedef uint32_t quint32; - -//define single or double precision reals and complex types -typedef float tSReal; -typedef double tDReal; - -typedef struct _sCplx -{ - tSReal re; - tSReal im; -}tSComplex; - -typedef struct _dCplx -{ - tDReal re; - tDReal im; -}tDComplex; - -typedef struct _isCplx -{ - qint16 re; - qint16 im; -}tStereo16; - -typedef struct _iICplx -{ - qint32 re; - qint32 im; -}tICpx32; - - -struct sSYSTEMTIME -{ - quint16 wYear; - quint16 wMonth; - quint16 wDayOfWeek; - quint16 wDay; - quint16 wHour; - quint16 wMinute; - quint16 wSecond; - quint16 wMilliseconds; -}; - - -typedef union -{ - struct bs - { - unsigned char b0; - unsigned char b1; - unsigned char b2; - unsigned char b3; - }bytes; - int all; -}tBtoL; - -typedef union -{ - struct bs - { - unsigned char b0; - unsigned char b1; - }bytes; - signed short sall; - unsigned short all; -}tBtoS; - -#ifdef FMDSP_USE_DOUBLE_PRECISION - #define TYPEREAL tDReal - #define TYPECPX tDComplex -#else - #define TYPEREAL tSReal - #define TYPECPX tSComplex -#endif - -#ifdef FMDSP_USE_DOUBLE_PRECISION - #define MSIN(x) sin(x) - #define MCOS(x) cos(x) - #define MPOW(x,y) pow(x,y) - #define MEXP(x) exp(x) - #define MFABS(x) fabs(x) - #define MLOG(x) log(x) - #define MLOG10(x) log10(x) - #define MSQRT(x) sqrt(x) - #define MATAN(x) atan(x) - #define MFMOD(x,y) fmod(x,y) - #define MATAN2(x,y) atan2(x,y) -#else - #define MSIN(x) sinf(x) - #define MCOS(x) cosf(x) - #define MPOW(x,y) powf(x,y) - #define MEXP(x) expf(x) - #define MFABS(x) fabsf(x) - #define MLOG(x) logf(x) - #define MLOG10(x) log10f(x) - #define MSQRT(x) sqrtf(x) - #define MATAN(x) atanf(x) - #define MFMOD(x,y) fmodf(x,y) - #define MATAN2(x,y) atan2f(x,y) -#endif - -#define TYPESTEREO16 tStereo16 -#define TYPEMONO16 qint16 - -//#define K_2PI (8.0*atan(1)) //maybe some compilers are't too smart to optimize out -#define K_2PI (2.0 * 3.14159265358979323846) -#define K_PI (3.14159265358979323846) -#define K_PI4 (K_PI/4.0) -#define K_PI2 (K_PI/2.0) -#define K_3PI4 (3.0*K_PI4) - -#ifndef NULL -#define NULL 0 -#endif - - -#endif // DATATYPES_H +////////////////////////////////////////////////////////////////////// +// datatypes.h: Common data type declarations +// +// History: +// 2010-09-15 Initial creation MSW +// 2011-03-27 Initial release +// 2013-07-28 Added single/double precision math macros +////////////////////////////////////////////////////////////////////// +#ifndef DATATYPES_H +#define DATATYPES_H + +#include +#include + +// uncomment to use double precision math +// #define FMDSP_USE_DOUBLE_PRECISION + +// uncomment to enable thread safety mechanisms +// #define FMDSP_THREAD_SAFE + +// Qt compatibility +// +typedef int8_t qint8; +typedef int16_t qint16; +typedef int32_t qint32; +typedef uint8_t quint8; +typedef uint16_t quint16; +typedef uint32_t quint32; + +//define single or double precision reals and complex types +typedef float tSReal; +typedef double tDReal; + +typedef struct _sCplx +{ + tSReal re; + tSReal im; +}tSComplex; + +typedef struct _dCplx +{ + tDReal re; + tDReal im; +}tDComplex; + +typedef struct _isCplx +{ + qint16 re; + qint16 im; +}tStereo16; + +typedef struct _iICplx +{ + qint32 re; + qint32 im; +}tICpx32; + + +struct sSYSTEMTIME +{ + quint16 wYear; + quint16 wMonth; + quint16 wDayOfWeek; + quint16 wDay; + quint16 wHour; + quint16 wMinute; + quint16 wSecond; + quint16 wMilliseconds; +}; + + +typedef union +{ + struct bs + { + unsigned char b0; + unsigned char b1; + unsigned char b2; + unsigned char b3; + }bytes; + int all; +}tBtoL; + +typedef union +{ + struct bs + { + unsigned char b0; + unsigned char b1; + }bytes; + signed short sall; + unsigned short all; +}tBtoS; + +#ifdef FMDSP_USE_DOUBLE_PRECISION + #define TYPEREAL tDReal + #define TYPECPX tDComplex +#else + #define TYPEREAL tSReal + #define TYPECPX tSComplex +#endif + +#ifdef FMDSP_USE_DOUBLE_PRECISION + #define MSIN(x) sin(x) + #define MCOS(x) cos(x) + #define MPOW(x,y) pow(x,y) + #define MEXP(x) exp(x) + #define MFABS(x) fabs(x) + #define MLOG(x) log(x) + #define MLOG10(x) log10(x) + #define MSQRT(x) sqrt(x) + #define MATAN(x) atan(x) + #define MFMOD(x,y) fmod(x,y) + #define MATAN2(x,y) atan2(x,y) +#else + #define MSIN(x) sinf(x) + #define MCOS(x) cosf(x) + #define MPOW(x,y) powf(x,y) + #define MEXP(x) expf(x) + #define MFABS(x) fabsf(x) + #define MLOG(x) logf(x) + #define MLOG10(x) log10f(x) + #define MSQRT(x) sqrtf(x) + #define MATAN(x) atanf(x) + #define MFMOD(x,y) fmodf(x,y) + #define MATAN2(x,y) atan2f(x,y) +#endif + +#define TYPESTEREO16 tStereo16 +#define TYPEMONO16 qint16 + +//#define K_2PI (8.0*atan(1)) //maybe some compilers are't too smart to optimize out +#define K_2PI (2.0 * 3.14159265358979323846) +#define K_PI (3.14159265358979323846) +#define K_PI4 (K_PI/4.0) +#define K_PI2 (K_PI/2.0) +#define K_3PI4 (3.0*K_PI4) + +#ifndef NULL +#define NULL 0 +#endif + + +#endif // DATATYPES_H diff --git a/src/fmdsp/demodulator.cpp b/src/fmdsp/demodulator.cpp index 27efe08..3a17e1d 100644 --- a/src/fmdsp/demodulator.cpp +++ b/src/fmdsp/demodulator.cpp @@ -1,315 +1,315 @@ -///////////////////////////////////////////////////////////////////// -// demodulator.cpp: implementation of the Cdemodulator class. -// -// This class implements the demodulation DSP functionality to take -//raw I/Q data from the radio, shift to baseband, decimate, demodulate, -//perform AGC, and send the audio to the sound card. -// -// History: -// 2010-09-15 Initial creation MSW -// 2011-03-27 Initial release -// 2013-07-28 Added single/double precision math macros -///////////////////////////////////////////////////////////////////// -//========================================================================================== -// + + + This Software is released under the "Simplified BSD License" + + + -//Copyright 2010 Moe Wheatley. All rights reserved. -// -//Redistribution and use in source and binary forms, with or without modification, are -//permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other materials -// provided with the distribution. -// -//THIS SOFTWARE IS PROVIDED BY Moe Wheatley ``AS IS'' AND ANY EXPRESS OR IMPLIED -//WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -//FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Moe Wheatley OR -//CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -//CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -//SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -//ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -//NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -//ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -//The views and conclusions contained in the software and documentation are those of the -//authors and should not be interpreted as representing official policies, either expressed -//or implied, of Moe Wheatley. -//========================================================================================== -#include "demodulator.h" - -////////////////////////////////////////////////////////////////// -// Constructor/Destructor -////////////////////////////////////////////////////////////////// -CDemodulator::CDemodulator() -{ - m_DownConverterOutputRate = 48000.0; - m_DemodOutputRate = 48000.0; - m_pDemodInBuf = new TYPECPX[MAX_INBUFSIZE]; - m_pDemodTmpBuf = new TYPECPX[MAX_INBUFSIZE]; - m_InBufPos = 0; - m_InBufLimit = 1000; - m_DemodMode = -1; - m_pFmDemod = NULL; - m_pWFmDemod = NULL; - m_USFm = true; - SetDemodFreq(0.0); -} - -CDemodulator::~CDemodulator() -{ - DeleteAllDemods(); - if(m_pDemodInBuf) - delete m_pDemodInBuf; - if(m_pDemodTmpBuf) - delete m_pDemodTmpBuf; -} - -////////////////////////////////////////////////////////////////// -// Deletes all demod objects -////////////////////////////////////////////////////////////////// -void CDemodulator::DeleteAllDemods() -{ - if(m_pFmDemod) - delete m_pFmDemod; - if(m_pWFmDemod) - delete m_pWFmDemod; - m_pFmDemod = NULL; - m_pWFmDemod = NULL; -} - -////////////////////////////////////////////////////////////////// -// Called to set/change the demodulator input sample rate -////////////////////////////////////////////////////////////////// -void CDemodulator::SetInputSampleRate(TYPEREAL InputRate) -{ - if(m_InputRate != InputRate) - { - m_InputRate = InputRate; - //change any demod parameters that may occur with sample rate change - switch(m_DemodMode) - { - case DEMOD_FM: - m_DownConverterOutputRate = m_DownConvert.SetDataRate(m_InputRate, m_DesiredMaxOutputBandwidth); - m_DemodOutputRate = m_DownConverterOutputRate; - if(m_pFmDemod) - m_pFmDemod->SetSampleRate(m_DownConverterOutputRate); - break; - case DEMOD_WFM: - m_DownConverterOutputRate = m_DownConvert.SetWfmDataRate(m_InputRate, 100000); - if(m_pWFmDemod) - m_DemodOutputRate = m_pWFmDemod->SetSampleRate(m_DownConverterOutputRate, m_USFm); - break; - } - } -} - -////////////////////////////////////////////////////////////////// -// Called to set/change the active Demod object -//or if a demod parameter or filter parameter changes -////////////////////////////////////////////////////////////////// -void CDemodulator::SetDemod(int Mode, tDemodInfo CurrentDemodInfo) -{ -#ifdef FMDSP_THREAD_SAFE - std::unique_lock lock(m_Mutex); -#endif - - m_DownConvert.SetQuality(CurrentDemodInfo.WfmDownsampleQuality); - - m_DemodInfo = CurrentDemodInfo; - if(m_DemodMode != Mode) //do only if changes - { - DeleteAllDemods(); //remove current demod object - m_DemodMode = Mode; - m_DesiredMaxOutputBandwidth = m_DemodInfo.HiCutmax; - - //now create correct demodulator - switch(m_DemodMode) - { - case DEMOD_FM: - m_DownConverterOutputRate = m_DownConvert.SetDataRate(m_InputRate, m_DesiredMaxOutputBandwidth); - m_pFmDemod = new CFmDemod(m_DownConverterOutputRate); - m_DemodOutputRate = m_DownConverterOutputRate; - break; - case DEMOD_WFM: - m_DownConverterOutputRate = m_DownConvert.SetWfmDataRate(m_InputRate, 100000); - m_pWFmDemod = new CWFmDemod(m_DownConverterOutputRate); - m_DemodOutputRate = m_pWFmDemod->GetDemodRate(); - break; - } - } - - if(m_DemodMode != DEMOD_WFM) - { - m_FastFIR.SetupParameters(m_DemodInfo.LowCut, m_DemodInfo.HiCut, 0, m_DownConverterOutputRate); - } - if( m_pFmDemod != NULL) - m_pFmDemod->SetSquelch(m_DemodInfo.SquelchValue); - //set input buffer limit so that decimated output is abt 10mSec or more of data - m_InBufLimit = static_cast((m_DemodOutputRate/100.0) * m_InputRate/m_DemodOutputRate); //process abt .01sec of output samples at a time - m_InBufLimit &= 0xFFFFFF00; //keep modulo 256 since decimation is only in power of 2 -} - -////////////////////////////////////////////////////////////////// -// Called with complex data from radio and performs the demodulation -// with MONO audio output -////////////////////////////////////////////////////////////////// -int CDemodulator::ProcessData(int InLength, TYPECPX* pInData, TYPEREAL* pOutData) -{ -int ret = 0; - -#ifdef FMDSP_THREAD_SAFE - std::unique_lock lock(m_Mutex); -#endif - - for(int i=0; i= m_InBufLimit) - { //when have enough samples, call demod routine sequence - - //perform baseband tuning and decimation - int n = m_DownConvert.ProcessData(m_InBufPos, m_pDemodInBuf, m_pDemodInBuf); - - if(m_DemodMode != DEMOD_WFM) - { - //perform main bandpass filtering - n = m_FastFIR.ProcessData(n, m_pDemodInBuf, m_pDemodTmpBuf); - MeasureSignalQuality(n, m_pDemodTmpBuf); - } - else - MeasureSignalQuality(n, m_pDemodInBuf); - - //perform the desired demod action - switch(m_DemodMode) - { - case DEMOD_FM: - n = m_pFmDemod->ProcessData(n, m_DemodInfo.HiCut, m_pDemodTmpBuf, pOutData ); - break; - case DEMOD_WFM: - n = m_pWFmDemod->ProcessData(n, m_pDemodInBuf, pOutData ); - break; - } - m_InBufPos = 0; - ret += n; - } - } - - return ret; -} - -////////////////////////////////////////////////////////////////// -// Called with complex data from radio and performs the demodulation -// with STEREO audio output -////////////////////////////////////////////////////////////////// -int CDemodulator::ProcessData(int InLength, TYPECPX* pInData, TYPECPX* pOutData) -{ -int ret = 0; - -#ifdef FMDSP_THREAD_SAFE - std::unique_lock lock(m_Mutex); -#endif - - for(int i=0; i= m_InBufLimit) - { //when have enough samples, call demod routine sequence - - //perform baseband tuning and decimation - int n = m_DownConvert.ProcessData(m_InBufPos, m_pDemodInBuf, m_pDemodInBuf); - - if(m_DemodMode != DEMOD_WFM) - { - //perform main bandpass filtering - n = m_FastFIR.ProcessData(n, m_pDemodInBuf, m_pDemodTmpBuf); - MeasureSignalQuality(n, m_pDemodTmpBuf); - } - else - MeasureSignalQuality(n, m_pDemodInBuf); - - //perform the desired demod action - switch(m_DemodMode) - { - case DEMOD_FM: - n = m_pFmDemod->ProcessData(n, m_DemodInfo.HiCut, m_pDemodTmpBuf, pOutData ); - break; - case DEMOD_WFM: - n = m_pWFmDemod->ProcessData(n, m_pDemodInBuf, pOutData ); - break; - } - - m_InBufPos = 0; - ret += n; - } - } - - return ret; -} - -// Added to provide a running signal quality calculations -void CDemodulator::MeasureSignalQuality(int length, TYPECPX* pInData) -{ - int sample_index = 0; // Index into current sample set - - if(length <= 0) return; - - // Get the level of the first sample - TYPEREAL& re = pInData[sample_index].re; - TYPEREAL& im = pInData[sample_index].im; - TYPEREAL level = (re * re) + (im * im); - - // First sample, reset sum and sum squared to new values - if(m_smeter_samples == 0) { - - m_smeter_sum = m_smeter_max = level; - m_smeter_variance_old_m = m_smeter_variance_new_m = level; - m_smeter_variance_old_s = m_smeter_variance_new_s = 0; - - sample_index = 1; - } - - // Process remaining samples - while(sample_index < length) { - - // Get the level of the next sample - re = pInData[sample_index].re; - im = pInData[sample_index].im; - level = (re * re) + (im * im); - - m_smeter_sum += level; - if(level > m_smeter_max) m_smeter_max = level; - m_smeter_samples++; - - m_smeter_variance_new_m = m_smeter_variance_old_m + (level - m_smeter_variance_old_m) / static_cast(m_smeter_samples); - m_smeter_variance_new_s = m_smeter_variance_old_s + (level - m_smeter_variance_old_m) * (level - m_smeter_variance_new_m); - m_smeter_variance_old_m = m_smeter_variance_new_m; - m_smeter_variance_old_s = m_smeter_variance_new_s; - - sample_index++; - } -} - -// Retrieves the signal levels from the demodulator and resets the statistics -void CDemodulator::GetSignalLevels(TYPEREAL& quality, TYPEREAL& snr) -{ - quality = snr = 0; - - // Signal quality is based on the coefficient of variation - if(m_smeter_samples > 1) { - - // Calcluate the variance, standard deviation, and coeficient of variation - TYPEREAL variance = m_smeter_variance_new_s / static_cast(m_smeter_samples - 1); - TYPEREAL sd = sqrt(variance); - TYPEREAL cv = sd / fabs(m_smeter_sum / static_cast(m_smeter_samples)); - quality = 1.0 - cv; - } - - // SNR is based on ratio of the mean and the maximum power levels - TYPEREAL mean = m_smeter_sum / static_cast(m_smeter_samples); - snr = mean / m_smeter_max; - - m_smeter_samples = 0; // Reset statistics on next pass -} +///////////////////////////////////////////////////////////////////// +// demodulator.cpp: implementation of the Cdemodulator class. +// +// This class implements the demodulation DSP functionality to take +//raw I/Q data from the radio, shift to baseband, decimate, demodulate, +//perform AGC, and send the audio to the sound card. +// +// History: +// 2010-09-15 Initial creation MSW +// 2011-03-27 Initial release +// 2013-07-28 Added single/double precision math macros +///////////////////////////////////////////////////////////////////// +//========================================================================================== +// + + + This Software is released under the "Simplified BSD License" + + + +//Copyright 2010 Moe Wheatley. All rights reserved. +// +//Redistribution and use in source and binary forms, with or without modification, are +//permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other materials +// provided with the distribution. +// +//THIS SOFTWARE IS PROVIDED BY Moe Wheatley ``AS IS'' AND ANY EXPRESS OR IMPLIED +//WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +//FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Moe Wheatley OR +//CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +//CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +//SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +//ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +//NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +//ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +//The views and conclusions contained in the software and documentation are those of the +//authors and should not be interpreted as representing official policies, either expressed +//or implied, of Moe Wheatley. +//========================================================================================== +#include "demodulator.h" + +////////////////////////////////////////////////////////////////// +// Constructor/Destructor +////////////////////////////////////////////////////////////////// +CDemodulator::CDemodulator() +{ + m_DownConverterOutputRate = 48000.0; + m_DemodOutputRate = 48000.0; + m_pDemodInBuf = new TYPECPX[MAX_INBUFSIZE]; + m_pDemodTmpBuf = new TYPECPX[MAX_INBUFSIZE]; + m_InBufPos = 0; + m_InBufLimit = 1000; + m_DemodMode = -1; + m_pFmDemod = NULL; + m_pWFmDemod = NULL; + m_USFm = true; + SetDemodFreq(0.0); +} + +CDemodulator::~CDemodulator() +{ + DeleteAllDemods(); + if(m_pDemodInBuf) + delete m_pDemodInBuf; + if(m_pDemodTmpBuf) + delete m_pDemodTmpBuf; +} + +////////////////////////////////////////////////////////////////// +// Deletes all demod objects +////////////////////////////////////////////////////////////////// +void CDemodulator::DeleteAllDemods() +{ + if(m_pFmDemod) + delete m_pFmDemod; + if(m_pWFmDemod) + delete m_pWFmDemod; + m_pFmDemod = NULL; + m_pWFmDemod = NULL; +} + +////////////////////////////////////////////////////////////////// +// Called to set/change the demodulator input sample rate +////////////////////////////////////////////////////////////////// +void CDemodulator::SetInputSampleRate(TYPEREAL InputRate) +{ + if(m_InputRate != InputRate) + { + m_InputRate = InputRate; + //change any demod parameters that may occur with sample rate change + switch(m_DemodMode) + { + case DEMOD_FM: + m_DownConverterOutputRate = m_DownConvert.SetDataRate(m_InputRate, m_DesiredMaxOutputBandwidth); + m_DemodOutputRate = m_DownConverterOutputRate; + if(m_pFmDemod) + m_pFmDemod->SetSampleRate(m_DownConverterOutputRate); + break; + case DEMOD_WFM: + m_DownConverterOutputRate = m_DownConvert.SetWfmDataRate(m_InputRate, 100000); + if(m_pWFmDemod) + m_DemodOutputRate = m_pWFmDemod->SetSampleRate(m_DownConverterOutputRate, m_USFm); + break; + } + } +} + +////////////////////////////////////////////////////////////////// +// Called to set/change the active Demod object +//or if a demod parameter or filter parameter changes +////////////////////////////////////////////////////////////////// +void CDemodulator::SetDemod(int Mode, tDemodInfo CurrentDemodInfo) +{ +#ifdef FMDSP_THREAD_SAFE + std::unique_lock lock(m_Mutex); +#endif + + m_DownConvert.SetQuality(CurrentDemodInfo.WfmDownsampleQuality); + + m_DemodInfo = CurrentDemodInfo; + if(m_DemodMode != Mode) //do only if changes + { + DeleteAllDemods(); //remove current demod object + m_DemodMode = Mode; + m_DesiredMaxOutputBandwidth = m_DemodInfo.HiCutmax; + + //now create correct demodulator + switch(m_DemodMode) + { + case DEMOD_FM: + m_DownConverterOutputRate = m_DownConvert.SetDataRate(m_InputRate, m_DesiredMaxOutputBandwidth); + m_pFmDemod = new CFmDemod(m_DownConverterOutputRate); + m_DemodOutputRate = m_DownConverterOutputRate; + break; + case DEMOD_WFM: + m_DownConverterOutputRate = m_DownConvert.SetWfmDataRate(m_InputRate, 100000); + m_pWFmDemod = new CWFmDemod(m_DownConverterOutputRate); + m_DemodOutputRate = m_pWFmDemod->GetDemodRate(); + break; + } + } + + if(m_DemodMode != DEMOD_WFM) + { + m_FastFIR.SetupParameters(m_DemodInfo.LowCut, m_DemodInfo.HiCut, 0, m_DownConverterOutputRate); + } + if( m_pFmDemod != NULL) + m_pFmDemod->SetSquelch(m_DemodInfo.SquelchValue); + //set input buffer limit so that decimated output is abt 10mSec or more of data + m_InBufLimit = static_cast((m_DemodOutputRate/100.0) * m_InputRate/m_DemodOutputRate); //process abt .01sec of output samples at a time + m_InBufLimit &= 0xFFFFFF00; //keep modulo 256 since decimation is only in power of 2 +} + +////////////////////////////////////////////////////////////////// +// Called with complex data from radio and performs the demodulation +// with MONO audio output +////////////////////////////////////////////////////////////////// +int CDemodulator::ProcessData(int InLength, TYPECPX* pInData, TYPEREAL* pOutData) +{ +int ret = 0; + +#ifdef FMDSP_THREAD_SAFE + std::unique_lock lock(m_Mutex); +#endif + + for(int i=0; i= m_InBufLimit) + { //when have enough samples, call demod routine sequence + + //perform baseband tuning and decimation + int n = m_DownConvert.ProcessData(m_InBufPos, m_pDemodInBuf, m_pDemodInBuf); + + if(m_DemodMode != DEMOD_WFM) + { + //perform main bandpass filtering + n = m_FastFIR.ProcessData(n, m_pDemodInBuf, m_pDemodTmpBuf); + MeasureSignalQuality(n, m_pDemodTmpBuf); + } + else + MeasureSignalQuality(n, m_pDemodInBuf); + + //perform the desired demod action + switch(m_DemodMode) + { + case DEMOD_FM: + n = m_pFmDemod->ProcessData(n, m_DemodInfo.HiCut, m_pDemodTmpBuf, pOutData ); + break; + case DEMOD_WFM: + n = m_pWFmDemod->ProcessData(n, m_pDemodInBuf, pOutData ); + break; + } + m_InBufPos = 0; + ret += n; + } + } + + return ret; +} + +////////////////////////////////////////////////////////////////// +// Called with complex data from radio and performs the demodulation +// with STEREO audio output +////////////////////////////////////////////////////////////////// +int CDemodulator::ProcessData(int InLength, TYPECPX* pInData, TYPECPX* pOutData) +{ +int ret = 0; + +#ifdef FMDSP_THREAD_SAFE + std::unique_lock lock(m_Mutex); +#endif + + for(int i=0; i= m_InBufLimit) + { //when have enough samples, call demod routine sequence + + //perform baseband tuning and decimation + int n = m_DownConvert.ProcessData(m_InBufPos, m_pDemodInBuf, m_pDemodInBuf); + + if(m_DemodMode != DEMOD_WFM) + { + //perform main bandpass filtering + n = m_FastFIR.ProcessData(n, m_pDemodInBuf, m_pDemodTmpBuf); + MeasureSignalQuality(n, m_pDemodTmpBuf); + } + else + MeasureSignalQuality(n, m_pDemodInBuf); + + //perform the desired demod action + switch(m_DemodMode) + { + case DEMOD_FM: + n = m_pFmDemod->ProcessData(n, m_DemodInfo.HiCut, m_pDemodTmpBuf, pOutData ); + break; + case DEMOD_WFM: + n = m_pWFmDemod->ProcessData(n, m_pDemodInBuf, pOutData ); + break; + } + + m_InBufPos = 0; + ret += n; + } + } + + return ret; +} + +// Added to provide a running signal quality calculations +void CDemodulator::MeasureSignalQuality(int length, TYPECPX* pInData) +{ + int sample_index = 0; // Index into current sample set + + if(length <= 0) return; + + // Get the level of the first sample + TYPEREAL& re = pInData[sample_index].re; + TYPEREAL& im = pInData[sample_index].im; + TYPEREAL level = (re * re) + (im * im); + + // First sample, reset sum and sum squared to new values + if(m_smeter_samples == 0) { + + m_smeter_sum = m_smeter_max = level; + m_smeter_variance_old_m = m_smeter_variance_new_m = level; + m_smeter_variance_old_s = m_smeter_variance_new_s = 0; + + sample_index = 1; + } + + // Process remaining samples + while(sample_index < length) { + + // Get the level of the next sample + re = pInData[sample_index].re; + im = pInData[sample_index].im; + level = (re * re) + (im * im); + + m_smeter_sum += level; + if(level > m_smeter_max) m_smeter_max = level; + m_smeter_samples++; + + m_smeter_variance_new_m = m_smeter_variance_old_m + (level - m_smeter_variance_old_m) / static_cast(m_smeter_samples); + m_smeter_variance_new_s = m_smeter_variance_old_s + (level - m_smeter_variance_old_m) * (level - m_smeter_variance_new_m); + m_smeter_variance_old_m = m_smeter_variance_new_m; + m_smeter_variance_old_s = m_smeter_variance_new_s; + + sample_index++; + } +} + +// Retrieves the signal levels from the demodulator and resets the statistics +void CDemodulator::GetSignalLevels(TYPEREAL& quality, TYPEREAL& snr) +{ + quality = snr = 0; + + // Signal quality is based on the coefficient of variation + if(m_smeter_samples > 1) { + + // Calcluate the variance, standard deviation, and coeficient of variation + TYPEREAL variance = m_smeter_variance_new_s / static_cast(m_smeter_samples - 1); + TYPEREAL sd = sqrt(variance); + TYPEREAL cv = sd / fabs(m_smeter_sum / static_cast(m_smeter_samples)); + quality = 1.0 - cv; + } + + // SNR is based on ratio of the mean and the maximum power levels + TYPEREAL mean = m_smeter_sum / static_cast(m_smeter_samples); + snr = mean / m_smeter_max; + + m_smeter_samples = 0; // Reset statistics on next pass +} diff --git a/src/fmdsp/demodulator.h b/src/fmdsp/demodulator.h index e5be848..67c9f83 100644 --- a/src/fmdsp/demodulator.h +++ b/src/fmdsp/demodulator.h @@ -1,139 +1,139 @@ -////////////////////////////////////////////////////////////////////// -// demodulator.h: interface for the CDemodulator class. -// -// History: -// 2010-09-15 Initial creation MSW -// 2011-03-27 Initial release -///////////////////////////////////////////////////////////////////// -//========================================================================================== -// + + + This Software is released under the "Simplified BSD License" + + + -//Copyright 2010 Moe Wheatley. All rights reserved. -// -//Redistribution and use in source and binary forms, with or without modification, are -//permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other materials -// provided with the distribution. -// -//THIS SOFTWARE IS PROVIDED BY Moe Wheatley ``AS IS'' AND ANY EXPRESS OR IMPLIED -//WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -//FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Moe Wheatley OR -//CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -//CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -//SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -//ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -//NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -//ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -//The views and conclusions contained in the software and documentation are those of the -//authors and should not be interpreted as representing official policies, either expressed -//or implied, of Moe Wheatley. -//============================================================================= -#ifndef DEMODULATOR_H -#define DEMODULATOR_H - -#include "downconvert.h" -#include "fastfir.h" -#include "fft.h" -#include "fmdemod.h" -#include "wfmdemod.h" - -#include -#include - -#define DEMOD_FM 2 -#define DEMOD_WFM 7 - -#define MAX_INBUFSIZE 250000 //maximum size of demod input buffer - //pick so that worst case decimation leaves - //reasonable number of samples to process -#define MAX_MAGBUFSIZE 32000 - -#define SMETER_FFT_SIZE 512 // Width of signal meter FFT - -typedef struct _sdmd -{ - int HiCut; - int HiCutmax; - int LowCut; - int SquelchValue; - - // Wideband FM only - enum DownsampleQuality WfmDownsampleQuality; - -}tDemodInfo; - -class CDemodulator -{ -public: - CDemodulator(); - virtual ~CDemodulator(); - - void SetInputSampleRate(TYPEREAL InputRate); - TYPEREAL GetOutputRate(){return m_DemodOutputRate;} - - void SetDemod(int Mode, tDemodInfo CurrentDemodInfo); - void SetDemodFreq(TYPEREAL Freq){m_DownConvert.SetFrequency(Freq);} - - //overloaded functions to perform demod mono or stereo - int ProcessData(int InLength, TYPECPX* pInData, TYPEREAL* pOutData); - int ProcessData(int InLength, TYPECPX* pInData, TYPECPX* pOutData); - - void SetUSFmVersion(bool USFm){m_USFm = USFm;} - bool GetUSFmVersion(){return m_USFm;} - - // expose the input buffer limit - int GetInputBufferLimit(void) const - { - return m_InBufLimit; - } - - //access to WFM mode status - int GetStereoLock(int* pPilotLock){ if(m_pWFmDemod) return m_pWFmDemod->GetStereoLock(pPilotLock); else return false;} - bool GetNextRdsGroupData(tRDS_GROUPS* pGroupData) - { - if(m_pWFmDemod) return m_pWFmDemod->GetNextRdsGroupData(pGroupData); else return false; - } - - // Gets the signal quality values - void GetSignalLevels(TYPEREAL& quality, TYPEREAL& snr); - -private: - void DeleteAllDemods(); - CDownConvert m_DownConvert; - CFastFIR m_FastFIR; -#ifdef FMDSP_THREAD_SAFE - mutable std::mutex m_Mutex; //for keeping threads from stomping on each other -#endif - tDemodInfo m_DemodInfo; - TYPEREAL m_InputRate = 0; - TYPEREAL m_DownConverterOutputRate; - TYPEREAL m_DemodOutputRate; - TYPEREAL m_DesiredMaxOutputBandwidth; - TYPECPX* m_pDemodInBuf; - TYPECPX* m_pDemodTmpBuf; - bool m_USFm; - int m_DemodMode; - int m_InBufPos; - int m_InBufLimit; - //pointers to all the various implemented demodulator classes - CFmDemod* m_pFmDemod; - CWFmDemod* m_pWFmDemod; - - // Signal quality calculations - void MeasureSignalQuality(int n, TYPECPX* pInData); - - int m_smeter_samples = 0; - TYPEREAL m_smeter_max = 0; - TYPEREAL m_smeter_sum = 0; - TYPEREAL m_smeter_variance_old_m = 0; - TYPEREAL m_smeter_variance_new_m = 0; - TYPEREAL m_smeter_variance_old_s = 0; - TYPEREAL m_smeter_variance_new_s = 0; -}; - -#endif // DEMODULATOR_H +////////////////////////////////////////////////////////////////////// +// demodulator.h: interface for the CDemodulator class. +// +// History: +// 2010-09-15 Initial creation MSW +// 2011-03-27 Initial release +///////////////////////////////////////////////////////////////////// +//========================================================================================== +// + + + This Software is released under the "Simplified BSD License" + + + +//Copyright 2010 Moe Wheatley. All rights reserved. +// +//Redistribution and use in source and binary forms, with or without modification, are +//permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other materials +// provided with the distribution. +// +//THIS SOFTWARE IS PROVIDED BY Moe Wheatley ``AS IS'' AND ANY EXPRESS OR IMPLIED +//WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +//FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Moe Wheatley OR +//CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +//CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +//SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +//ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +//NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +//ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +//The views and conclusions contained in the software and documentation are those of the +//authors and should not be interpreted as representing official policies, either expressed +//or implied, of Moe Wheatley. +//============================================================================= +#ifndef DEMODULATOR_H +#define DEMODULATOR_H + +#include "downconvert.h" +#include "fastfir.h" +#include "fft.h" +#include "fmdemod.h" +#include "wfmdemod.h" + +#include +#include + +#define DEMOD_FM 2 +#define DEMOD_WFM 7 + +#define MAX_INBUFSIZE 250000 //maximum size of demod input buffer + //pick so that worst case decimation leaves + //reasonable number of samples to process +#define MAX_MAGBUFSIZE 32000 + +#define SMETER_FFT_SIZE 512 // Width of signal meter FFT + +typedef struct _sdmd +{ + int HiCut; + int HiCutmax; + int LowCut; + int SquelchValue; + + // Wideband FM only + enum DownsampleQuality WfmDownsampleQuality; + +}tDemodInfo; + +class CDemodulator +{ +public: + CDemodulator(); + virtual ~CDemodulator(); + + void SetInputSampleRate(TYPEREAL InputRate); + TYPEREAL GetOutputRate(){return m_DemodOutputRate;} + + void SetDemod(int Mode, tDemodInfo CurrentDemodInfo); + void SetDemodFreq(TYPEREAL Freq){m_DownConvert.SetFrequency(Freq);} + + //overloaded functions to perform demod mono or stereo + int ProcessData(int InLength, TYPECPX* pInData, TYPEREAL* pOutData); + int ProcessData(int InLength, TYPECPX* pInData, TYPECPX* pOutData); + + void SetUSFmVersion(bool USFm){m_USFm = USFm;} + bool GetUSFmVersion(){return m_USFm;} + + // expose the input buffer limit + int GetInputBufferLimit(void) const + { + return m_InBufLimit; + } + + //access to WFM mode status + int GetStereoLock(int* pPilotLock){ if(m_pWFmDemod) return m_pWFmDemod->GetStereoLock(pPilotLock); else return false;} + bool GetNextRdsGroupData(tRDS_GROUPS* pGroupData) + { + if(m_pWFmDemod) return m_pWFmDemod->GetNextRdsGroupData(pGroupData); else return false; + } + + // Gets the signal quality values + void GetSignalLevels(TYPEREAL& quality, TYPEREAL& snr); + +private: + void DeleteAllDemods(); + CDownConvert m_DownConvert; + CFastFIR m_FastFIR; +#ifdef FMDSP_THREAD_SAFE + mutable std::mutex m_Mutex; //for keeping threads from stomping on each other +#endif + tDemodInfo m_DemodInfo; + TYPEREAL m_InputRate = 0; + TYPEREAL m_DownConverterOutputRate; + TYPEREAL m_DemodOutputRate; + TYPEREAL m_DesiredMaxOutputBandwidth; + TYPECPX* m_pDemodInBuf; + TYPECPX* m_pDemodTmpBuf; + bool m_USFm; + int m_DemodMode; + int m_InBufPos; + int m_InBufLimit; + //pointers to all the various implemented demodulator classes + CFmDemod* m_pFmDemod; + CWFmDemod* m_pWFmDemod; + + // Signal quality calculations + void MeasureSignalQuality(int n, TYPECPX* pInData); + + int m_smeter_samples = 0; + TYPEREAL m_smeter_max = 0; + TYPEREAL m_smeter_sum = 0; + TYPEREAL m_smeter_variance_old_m = 0; + TYPEREAL m_smeter_variance_new_m = 0; + TYPEREAL m_smeter_variance_old_s = 0; + TYPEREAL m_smeter_variance_new_s = 0; +}; + +#endif // DEMODULATOR_H diff --git a/src/fmdsp/downconvert.cpp b/src/fmdsp/downconvert.cpp index ef75384..1ef5d91 100644 --- a/src/fmdsp/downconvert.cpp +++ b/src/fmdsp/downconvert.cpp @@ -1,524 +1,524 @@ -// downconvert.cpp: implementation of the CDownConvert class. -// -// This class takes I/Q baseband data and performs tuning -//(Frequency shifting of the baseband signal) as well as -// decimation in powers of 2 after the shifting. -// -// History: -// 2010-09-15 Initial creation MSW -// 2011-03-27 Initial release -// 2011-04-20 Changed some scope resolution operators to allow compiling with different compilers -// 2013-02-01 Fixed issue with missing first coef of HB calculation -// 2013-07-28 Added single/double precision math macros -////////////////////////////////////////////////////////////////////// -//========================================================================================== -// + + + This Software is released under the "Simplified BSD License" + + + -//Copyright 2010 Moe Wheatley. All rights reserved. -// -//Redistribution and use in source and binary forms, with or without modification, are -//permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other materials -// provided with the distribution. -// -//THIS SOFTWARE IS PROVIDED BY Moe Wheatley ``AS IS'' AND ANY EXPRESS OR IMPLIED -//WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -//FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Moe Wheatley OR -//CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -//CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -//SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -//ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -//NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -//ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -//The views and conclusions contained in the software and documentation are those of the -//authors and should not be interpreted as representing official policies, either expressed -//or implied, of Moe Wheatley. -//========================================================================================== -#include "downconvert.h" -#include "filtercoef.h" -#include -#include -#include - -//pick a method of calculating the NCO -#define NCO_LIB 0 //normal sin cos library (188nS) -#define NCO_OSC 1 //quadrature oscillator (25nS) -#define NCO_VCASM 0 //Visual C assembly call to floating point sin/cos instruction -#define NCO_GCCASM 0 //GCC assembly call to floating point sin/cos instruction (100nS) - -#define MIN_OUTPUT_RATE (7900.0*2.0) - -#define MAX_HALF_BAND_BUFSIZE 32768 - -////////////////////////////////////////////////////////////////////// -// Construction/Destruction -////////////////////////////////////////////////////////////////////// -CDownConvert::CDownConvert() -{ -int i; - m_NcoInc = 0.0; - m_NcoTime = 0.0; - m_NcoFreq = 0.0; - m_InRate = 100000.0; - m_MaxBW = 10000.0; - for(i=0; i lock(m_Mutex); - #endif - - DeleteFilters(); - //loop until closest output rate is found and list of pointers to decimate by 2 stages is generated - while( (f > (m_MaxBW / HB51TAP_MAX) ) && (f > MIN_OUTPUT_RATE) ) - { - if(f >= (m_MaxBW / CIC3_MAX) ) //See if can use CIC order 3 - m_pDecimatorPtrs[n++] = - new CCicN3DecimateBy2; - else if(f >= (m_MaxBW / HB11TAP_MAX) ) //See if can use fixed 11 Tap Halfband - m_pDecimatorPtrs[n++] = - new CHalfBand11TapDecimateBy2(); - else if(f >= (m_MaxBW / HB15TAP_MAX) ) //See if can use Halfband 15 Tap - m_pDecimatorPtrs[n++] = - new CHalfBandDecimateBy2(HB15TAP_LENGTH, HB15TAP_H); - else if(f >= (m_MaxBW / HB19TAP_MAX) ) //See if can use Halfband 19 Tap - m_pDecimatorPtrs[n++] = - new CHalfBandDecimateBy2(HB19TAP_LENGTH, HB19TAP_H); - else if(f >= (m_MaxBW / HB23TAP_MAX) ) //See if can use Halfband 23 Tap - m_pDecimatorPtrs[n++] = - new CHalfBandDecimateBy2(HB23TAP_LENGTH, HB23TAP_H); - else if(f >= (m_MaxBW / HB27TAP_MAX) ) //See if can use Halfband 27 Tap - m_pDecimatorPtrs[n++] = - new CHalfBandDecimateBy2(HB27TAP_LENGTH, HB27TAP_H); - else if(f >= (m_MaxBW / HB31TAP_MAX) ) //See if can use Halfband 31 Tap - m_pDecimatorPtrs[n++] = - new CHalfBandDecimateBy2(HB31TAP_LENGTH, HB31TAP_H); - else if(f >= (m_MaxBW / HB35TAP_MAX) ) //See if can use Halfband 35 Tap - m_pDecimatorPtrs[n++] = - new CHalfBandDecimateBy2(HB35TAP_LENGTH, HB35TAP_H); - else if(f >= (m_MaxBW / HB39TAP_MAX) ) //See if can use Halfband 39 Tap - m_pDecimatorPtrs[n++] = - new CHalfBandDecimateBy2(HB39TAP_LENGTH, HB39TAP_H); - else if(f >= (m_MaxBW / HB43TAP_MAX) ) //See if can use Halfband 43 Tap - m_pDecimatorPtrs[n++] = - new CHalfBandDecimateBy2(HB43TAP_LENGTH, HB43TAP_H); - else if(f >= (m_MaxBW / HB47TAP_MAX) ) //See if can use Halfband 47 Tap - m_pDecimatorPtrs[n++] = - new CHalfBandDecimateBy2(HB47TAP_LENGTH, HB47TAP_H); - else if(f >= (m_MaxBW / HB51TAP_MAX) ) //See if can use Halfband 51 Tap - m_pDecimatorPtrs[n++] = - new CHalfBandDecimateBy2(HB51TAP_LENGTH, HB51TAP_H); - f /= 2.0; - } - - #ifdef FMDSP_THREAD_SAFE - lock.unlock(); - #endif - - m_OutputRate = f; - SetFrequency(m_NcoFreq); - } - return m_OutputRate; -} - -////////////////////////////////////////////////////////////////////// -// Calculates sequence and number of decimation stages for WBFM based on -// input sample rate and desired output bandwidth. Returns final output rate -//from divide by 2 stages. -////////////////////////////////////////////////////////////////////// -TYPEREAL CDownConvert::SetWfmDataRate(TYPEREAL InRate, TYPEREAL MaxBW) -{ -int n = 0; -TYPEREAL f = InRate; - if( (m_InRate!=InRate) || - (m_MaxBW!=MaxBW) ) - { - m_InRate = InRate; - m_MaxBW = MaxBW; - - #ifdef FMDSP_THREAD_SAFE - std::unique_lock lock(m_Mutex); - #endif - - DeleteFilters(); - - //loop until closest output rate is found and list of pointers to decimate by 2 stages is generated - while( (f > 400000.0) ) - { - switch(m_Quality) { - - // HIGH: 51 tap - case DownsampleQuality::High: - m_pDecimatorPtrs[n++] = new CDownConvert::CHalfBandDecimateBy2(HB51TAP_LENGTH, HB51TAP_H); - break; - - // MEDIUM: 27 tap - case DownsampleQuality::Medium: - m_pDecimatorPtrs[n++] = new CHalfBandDecimateBy2(HB27TAP_LENGTH, HB27TAP_H); - break; - - // LOW: 11 tap - case DownsampleQuality::Low: - m_pDecimatorPtrs[n++] = new CDownConvert::CHalfBand11TapDecimateBy2(); - break; - - // DEFAULT: 51 tap - default: - m_pDecimatorPtrs[n++] = new CDownConvert::CHalfBandDecimateBy2(HB51TAP_LENGTH, HB51TAP_H); - break; - } - - f /= 2.0; - } - - m_OutputRate = f; - #ifdef FMDSP_THREAD_SAFE - lock.unlock(); - #endif - - SetFrequency(m_NcoFreq); - } - return m_OutputRate; -} - -////////////////////////////////////////////////////////////////////// -// Processes 'InLength' I/Q samples of 'pInData' buffer -// and places in 'pOutData' buffer. -// Returns number of samples available in output buffer. -// Make sure number of input samples is large enough to have enough -// output samples to process in following stages since decimation -// process reduces the number of output samples per block. -// Also InLength must be a multiple of 2^N where N is the maximum -// decimation by 2 stages expected. -// ~50nSec/sample at decimation by 128 -////////////////////////////////////////////////////////////////////// -int CDownConvert::ProcessData(int InLength, TYPECPX* pInData, TYPECPX* pOutData) -{ -int i,j; -TYPECPX dtmp; -TYPECPX Osc; - -#if (NCO_VCASM || NCO_GCCASM) -TYPEREAL dPhaseAcc = m_NcoTime; -TYPEREAL dASMCos = 0.0; -TYPEREAL dASMSin = 0.0; -TYPEREAL* pdCosAns = &dASMCos; -TYPEREAL* pdSinAns = &dASMSin; -#endif - -//263uS using sin/cos or 70uS using quadrature osc or 200uS using _asm - for(i=0; i lock(m_Mutex); -#endif - - while(m_pDecimatorPtrs[j]) - { - n = m_pDecimatorPtrs[j++]->DecBy2(n, pInData, pInData); - - } -#ifdef FMDSP_THREAD_SAFE - lock.unlock(); -#endif - for(i=0; i +#include +#include + +//pick a method of calculating the NCO +#define NCO_LIB 0 //normal sin cos library (188nS) +#define NCO_OSC 1 //quadrature oscillator (25nS) +#define NCO_VCASM 0 //Visual C assembly call to floating point sin/cos instruction +#define NCO_GCCASM 0 //GCC assembly call to floating point sin/cos instruction (100nS) + +#define MIN_OUTPUT_RATE (7900.0*2.0) + +#define MAX_HALF_BAND_BUFSIZE 32768 + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// +CDownConvert::CDownConvert() +{ +int i; + m_NcoInc = 0.0; + m_NcoTime = 0.0; + m_NcoFreq = 0.0; + m_InRate = 100000.0; + m_MaxBW = 10000.0; + for(i=0; i lock(m_Mutex); + #endif + + DeleteFilters(); + //loop until closest output rate is found and list of pointers to decimate by 2 stages is generated + while( (f > (m_MaxBW / HB51TAP_MAX) ) && (f > MIN_OUTPUT_RATE) ) + { + if(f >= (m_MaxBW / CIC3_MAX) ) //See if can use CIC order 3 + m_pDecimatorPtrs[n++] = + new CCicN3DecimateBy2; + else if(f >= (m_MaxBW / HB11TAP_MAX) ) //See if can use fixed 11 Tap Halfband + m_pDecimatorPtrs[n++] = + new CHalfBand11TapDecimateBy2(); + else if(f >= (m_MaxBW / HB15TAP_MAX) ) //See if can use Halfband 15 Tap + m_pDecimatorPtrs[n++] = + new CHalfBandDecimateBy2(HB15TAP_LENGTH, HB15TAP_H); + else if(f >= (m_MaxBW / HB19TAP_MAX) ) //See if can use Halfband 19 Tap + m_pDecimatorPtrs[n++] = + new CHalfBandDecimateBy2(HB19TAP_LENGTH, HB19TAP_H); + else if(f >= (m_MaxBW / HB23TAP_MAX) ) //See if can use Halfband 23 Tap + m_pDecimatorPtrs[n++] = + new CHalfBandDecimateBy2(HB23TAP_LENGTH, HB23TAP_H); + else if(f >= (m_MaxBW / HB27TAP_MAX) ) //See if can use Halfband 27 Tap + m_pDecimatorPtrs[n++] = + new CHalfBandDecimateBy2(HB27TAP_LENGTH, HB27TAP_H); + else if(f >= (m_MaxBW / HB31TAP_MAX) ) //See if can use Halfband 31 Tap + m_pDecimatorPtrs[n++] = + new CHalfBandDecimateBy2(HB31TAP_LENGTH, HB31TAP_H); + else if(f >= (m_MaxBW / HB35TAP_MAX) ) //See if can use Halfband 35 Tap + m_pDecimatorPtrs[n++] = + new CHalfBandDecimateBy2(HB35TAP_LENGTH, HB35TAP_H); + else if(f >= (m_MaxBW / HB39TAP_MAX) ) //See if can use Halfband 39 Tap + m_pDecimatorPtrs[n++] = + new CHalfBandDecimateBy2(HB39TAP_LENGTH, HB39TAP_H); + else if(f >= (m_MaxBW / HB43TAP_MAX) ) //See if can use Halfband 43 Tap + m_pDecimatorPtrs[n++] = + new CHalfBandDecimateBy2(HB43TAP_LENGTH, HB43TAP_H); + else if(f >= (m_MaxBW / HB47TAP_MAX) ) //See if can use Halfband 47 Tap + m_pDecimatorPtrs[n++] = + new CHalfBandDecimateBy2(HB47TAP_LENGTH, HB47TAP_H); + else if(f >= (m_MaxBW / HB51TAP_MAX) ) //See if can use Halfband 51 Tap + m_pDecimatorPtrs[n++] = + new CHalfBandDecimateBy2(HB51TAP_LENGTH, HB51TAP_H); + f /= 2.0; + } + + #ifdef FMDSP_THREAD_SAFE + lock.unlock(); + #endif + + m_OutputRate = f; + SetFrequency(m_NcoFreq); + } + return m_OutputRate; +} + +////////////////////////////////////////////////////////////////////// +// Calculates sequence and number of decimation stages for WBFM based on +// input sample rate and desired output bandwidth. Returns final output rate +//from divide by 2 stages. +////////////////////////////////////////////////////////////////////// +TYPEREAL CDownConvert::SetWfmDataRate(TYPEREAL InRate, TYPEREAL MaxBW) +{ +int n = 0; +TYPEREAL f = InRate; + if( (m_InRate!=InRate) || + (m_MaxBW!=MaxBW) ) + { + m_InRate = InRate; + m_MaxBW = MaxBW; + + #ifdef FMDSP_THREAD_SAFE + std::unique_lock lock(m_Mutex); + #endif + + DeleteFilters(); + + //loop until closest output rate is found and list of pointers to decimate by 2 stages is generated + while( (f > 400000.0) ) + { + switch(m_Quality) { + + // HIGH: 51 tap + case DownsampleQuality::High: + m_pDecimatorPtrs[n++] = new CDownConvert::CHalfBandDecimateBy2(HB51TAP_LENGTH, HB51TAP_H); + break; + + // MEDIUM: 27 tap + case DownsampleQuality::Medium: + m_pDecimatorPtrs[n++] = new CHalfBandDecimateBy2(HB27TAP_LENGTH, HB27TAP_H); + break; + + // LOW: 11 tap + case DownsampleQuality::Low: + m_pDecimatorPtrs[n++] = new CDownConvert::CHalfBand11TapDecimateBy2(); + break; + + // DEFAULT: 51 tap + default: + m_pDecimatorPtrs[n++] = new CDownConvert::CHalfBandDecimateBy2(HB51TAP_LENGTH, HB51TAP_H); + break; + } + + f /= 2.0; + } + + m_OutputRate = f; + #ifdef FMDSP_THREAD_SAFE + lock.unlock(); + #endif + + SetFrequency(m_NcoFreq); + } + return m_OutputRate; +} + +////////////////////////////////////////////////////////////////////// +// Processes 'InLength' I/Q samples of 'pInData' buffer +// and places in 'pOutData' buffer. +// Returns number of samples available in output buffer. +// Make sure number of input samples is large enough to have enough +// output samples to process in following stages since decimation +// process reduces the number of output samples per block. +// Also InLength must be a multiple of 2^N where N is the maximum +// decimation by 2 stages expected. +// ~50nSec/sample at decimation by 128 +////////////////////////////////////////////////////////////////////// +int CDownConvert::ProcessData(int InLength, TYPECPX* pInData, TYPECPX* pOutData) +{ +int i,j; +TYPECPX dtmp; +TYPECPX Osc; + +#if (NCO_VCASM || NCO_GCCASM) +TYPEREAL dPhaseAcc = m_NcoTime; +TYPEREAL dASMCos = 0.0; +TYPEREAL dASMSin = 0.0; +TYPEREAL* pdCosAns = &dASMCos; +TYPEREAL* pdSinAns = &dASMSin; +#endif + +//263uS using sin/cos or 70uS using quadrature osc or 200uS using _asm + for(i=0; i lock(m_Mutex); +#endif + + while(m_pDecimatorPtrs[j]) + { + n = m_pDecimatorPtrs[j++]->DecBy2(n, pInData, pInData); + + } +#ifdef FMDSP_THREAD_SAFE + lock.unlock(); +#endif + for(i=0; i - - -#define MAX_DECSTAGES 10 //one more than max to make sure is a null at end of list - -enum class DownsampleQuality -{ - Low = 0, // 11 tap - Medium = 1, // 27 tap - High = 2, // 51 tap -}; - -////////////////////////////////////////////////////////////////////////////////// -// Main Downconverter Class -////////////////////////////////////////////////////////////////////////////////// -class CDownConvert -{ -public: - CDownConvert(); - virtual ~CDownConvert(); - void SetFrequency(TYPEREAL NcoFreq); - int ProcessData(int InLength, TYPECPX* pInData, TYPECPX* pOutData); - TYPEREAL SetDataRate(TYPEREAL InRate, TYPEREAL MaxBW); - TYPEREAL SetWfmDataRate(TYPEREAL InRate, TYPEREAL MaxBW); - void SetQuality(enum DownsampleQuality Quality) { m_Quality = Quality; } - -private: - //////////// - //pure abstract base class for all the different types of decimate by 2 stages - //DecBy2 function is defined in derived classes - //////////// - class CDec2 - { - public: - CDec2(){} - virtual ~CDec2(){} - virtual int DecBy2(int InLength, TYPECPX* pInData, TYPECPX* pOutData) = 0; - }; - - //////////// - //private class for the Half Band decimate by 2 stages - //////////// - class CHalfBandDecimateBy2 : public CDec2 - { - public: - CHalfBandDecimateBy2(int len,const TYPEREAL* pCoef); - ~CHalfBandDecimateBy2(){if(m_pHBFirBuf) delete m_pHBFirBuf;} - int DecBy2(int InLength, TYPECPX* pInData, TYPECPX* pOutData); - TYPECPX* m_pHBFirBuf; - int m_FirLength; - const TYPEREAL* m_pCoef; - }; - - - //////////// - //private class for the fixed 11 tap Half Band decimate by 2 stages - //////////// - class CHalfBand11TapDecimateBy2 : public CDec2 - { - public: - CHalfBand11TapDecimateBy2(); - ~CHalfBand11TapDecimateBy2(){} - int DecBy2(int InLength, TYPECPX* pInData, TYPECPX* pOutData); - TYPEREAL H0; //unwrapped coeeficients - TYPEREAL H2; - TYPEREAL H4; - TYPEREAL H5; - TYPEREAL H6; - TYPEREAL H8; - TYPEREAL H10; - TYPECPX d0; //unwrapped delay buffer - TYPECPX d1; - TYPECPX d2; - TYPECPX d3; - TYPECPX d4; - TYPECPX d5; - TYPECPX d6; - TYPECPX d7; - TYPECPX d8; - TYPECPX d9; - }; - - //////////// - //private class for the N=3 CIC decimate by 2 stages - //////////// - class CCicN3DecimateBy2 : public CDec2 - { - public: - CCicN3DecimateBy2(); - ~CCicN3DecimateBy2(){} - int DecBy2(int InLength, TYPECPX* pInData, TYPECPX* pOutData); - TYPECPX m_Xodd; - TYPECPX m_Xeven; - }; - -private: - //private helper functions - void DeleteFilters(); - - enum DownsampleQuality m_Quality = DownsampleQuality::High; - - TYPEREAL m_OutputRate; - TYPEREAL m_NcoFreq; - TYPEREAL m_NcoInc; - TYPEREAL m_NcoTime; - TYPEREAL m_InRate; - TYPEREAL m_MaxBW; - TYPECPX m_Osc1; - TYPEREAL m_OscCos; - TYPEREAL m_OscSin; -#ifdef FMDSP_THREAD_SAFE - mutable std::mutex m_Mutex; //for keeping threads from stomping on each other -#endif - //array of pointers for performing decimate by 2 stages - CDec2* m_pDecimatorPtrs[MAX_DECSTAGES]; - -}; - -#endif // DOWNCONVERT_H +// downconvert.h: interface for the CDownConvert class. +// +// This class takes I/Q baseband data and performs tuning +//(Frequency shifting of the baseband signal) as well as +// decimation in powers of 2 after the shifting. +// +// History: +// 2010-09-15 Initial creation MSW +// 2011-03-27 Initial release +////////////////////////////////////////////////////////////////////// +//========================================================================================== +// + + + This Software is released under the "Simplified BSD License" + + + +//Copyright 2010 Moe Wheatley. All rights reserved. +// +//Redistribution and use in source and binary forms, with or without modification, are +//permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other materials +// provided with the distribution. +// +//THIS SOFTWARE IS PROVIDED BY Moe Wheatley ``AS IS'' AND ANY EXPRESS OR IMPLIED +//WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +//FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Moe Wheatley OR +//CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +//CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +//SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +//ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +//NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +//ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +//The views and conclusions contained in the software and documentation are those of the +//authors and should not be interpreted as representing official policies, either expressed +//or implied, of Moe Wheatley. +//============================================================================= +#ifndef DOWNCONVERT_H +#define DOWNCONVERT_H + +#include "datatypes.h" +#include + + +#define MAX_DECSTAGES 10 //one more than max to make sure is a null at end of list + +enum class DownsampleQuality +{ + Low = 0, // 11 tap + Medium = 1, // 27 tap + High = 2, // 51 tap +}; + +////////////////////////////////////////////////////////////////////////////////// +// Main Downconverter Class +////////////////////////////////////////////////////////////////////////////////// +class CDownConvert +{ +public: + CDownConvert(); + virtual ~CDownConvert(); + void SetFrequency(TYPEREAL NcoFreq); + int ProcessData(int InLength, TYPECPX* pInData, TYPECPX* pOutData); + TYPEREAL SetDataRate(TYPEREAL InRate, TYPEREAL MaxBW); + TYPEREAL SetWfmDataRate(TYPEREAL InRate, TYPEREAL MaxBW); + void SetQuality(enum DownsampleQuality Quality) { m_Quality = Quality; } + +private: + //////////// + //pure abstract base class for all the different types of decimate by 2 stages + //DecBy2 function is defined in derived classes + //////////// + class CDec2 + { + public: + CDec2(){} + virtual ~CDec2(){} + virtual int DecBy2(int InLength, TYPECPX* pInData, TYPECPX* pOutData) = 0; + }; + + //////////// + //private class for the Half Band decimate by 2 stages + //////////// + class CHalfBandDecimateBy2 : public CDec2 + { + public: + CHalfBandDecimateBy2(int len,const TYPEREAL* pCoef); + ~CHalfBandDecimateBy2(){if(m_pHBFirBuf) delete m_pHBFirBuf;} + int DecBy2(int InLength, TYPECPX* pInData, TYPECPX* pOutData); + TYPECPX* m_pHBFirBuf; + int m_FirLength; + const TYPEREAL* m_pCoef; + }; + + + //////////// + //private class for the fixed 11 tap Half Band decimate by 2 stages + //////////// + class CHalfBand11TapDecimateBy2 : public CDec2 + { + public: + CHalfBand11TapDecimateBy2(); + ~CHalfBand11TapDecimateBy2(){} + int DecBy2(int InLength, TYPECPX* pInData, TYPECPX* pOutData); + TYPEREAL H0; //unwrapped coeeficients + TYPEREAL H2; + TYPEREAL H4; + TYPEREAL H5; + TYPEREAL H6; + TYPEREAL H8; + TYPEREAL H10; + TYPECPX d0; //unwrapped delay buffer + TYPECPX d1; + TYPECPX d2; + TYPECPX d3; + TYPECPX d4; + TYPECPX d5; + TYPECPX d6; + TYPECPX d7; + TYPECPX d8; + TYPECPX d9; + }; + + //////////// + //private class for the N=3 CIC decimate by 2 stages + //////////// + class CCicN3DecimateBy2 : public CDec2 + { + public: + CCicN3DecimateBy2(); + ~CCicN3DecimateBy2(){} + int DecBy2(int InLength, TYPECPX* pInData, TYPECPX* pOutData); + TYPECPX m_Xodd; + TYPECPX m_Xeven; + }; + +private: + //private helper functions + void DeleteFilters(); + + enum DownsampleQuality m_Quality = DownsampleQuality::High; + + TYPEREAL m_OutputRate; + TYPEREAL m_NcoFreq; + TYPEREAL m_NcoInc; + TYPEREAL m_NcoTime; + TYPEREAL m_InRate; + TYPEREAL m_MaxBW; + TYPECPX m_Osc1; + TYPEREAL m_OscCos; + TYPEREAL m_OscSin; +#ifdef FMDSP_THREAD_SAFE + mutable std::mutex m_Mutex; //for keeping threads from stomping on each other +#endif + //array of pointers for performing decimate by 2 stages + CDec2* m_pDecimatorPtrs[MAX_DECSTAGES]; + +}; + +#endif // DOWNCONVERT_H diff --git a/src/fmdsp/fastfir.cpp b/src/fmdsp/fastfir.cpp index 7076758..416222f 100644 --- a/src/fmdsp/fastfir.cpp +++ b/src/fmdsp/fastfir.cpp @@ -1,303 +1,303 @@ -////////////////////////////////////////////////////////////////////// -// fastfir.cpp: implementation of the CFastFIR class. -// -// This class implements a FIR Bandpass filter using a FFT convolution algorithm -//The filter is complex and is specified with 3 parameters: -// sample frequency, Hicut and Lowcut frequency -// -//Uses FFT overlap and save method of implementing the FIR. -//For best performance use FIR size 4*FIR <= FFT <= 8*FIR -//If need output to be power of 2 then FIR must = 1/2FFT size -// -// History: -// 2010-09-15 Initial creation MSW -// 2011-03-27 Initial release -// 2011-11-03 Fixed m_pFFTOverlapBuf initialization bug -// 2012-08-06 Fixed m_pWindowTbl sizing problem -// 2013-07-28 Added single/double precision math macros -////////////////////////////////////////////////////////////////////// -//========================================================================================== -// + + + This Software is released under the "Simplified BSD License" + + + -//Copyright 2010 Moe Wheatley. All rights reserved. -// -//Redistribution and use in source and binary forms, with or without modification, are -//permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other materials -// provided with the distribution. -// -//THIS SOFTWARE IS PROVIDED BY Moe Wheatley ``AS IS'' AND ANY EXPRESS OR IMPLIED -//WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -//FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Moe Wheatley OR -//CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -//CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -//SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -//ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -//NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -//ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -//The views and conclusions contained in the software and documentation are those of the -//authors and should not be interpreted as representing official policies, either expressed -//or implied, of Moe Wheatley. -//========================================================================================== -#include "fastfir.h" -#include - - -////////////////////////////////////////////////////////////////////// -// Local Defines -////////////////////////////////////////////////////////////////////// -#define CONV_FFT_SIZE 2048 //must be power of 2 -#define CONV_FIR_SIZE 1025 //must be <= FFT size. Make 1/2 +1 if want - //output to be in power of 2 - - -#define CONV_INBUF_SIZE (CONV_FFT_SIZE+CONV_FIR_SIZE-1) - - -////////////////////////////////////////////////////////////////////// -// Construction/Destruction -////////////////////////////////////////////////////////////////////// - -CFastFIR::CFastFIR() -{ -int i; - m_pWindowTbl = NULL; - m_pFFTBuf = NULL; - m_pFFTOverlapBuf = NULL; - m_pFilterCoef = NULL; - //allocate internal buffer space on Heap - m_pWindowTbl = new TYPEREAL[CONV_FIR_SIZE]; - m_pFilterCoef = new TYPECPX[CONV_FFT_SIZE]; - m_pFFTBuf = new TYPECPX[CONV_FFT_SIZE]; - m_pFFTOverlapBuf = new TYPECPX[CONV_FIR_SIZE]; - - if(!m_pWindowTbl || !m_pFilterCoef || !m_pFFTBuf || !m_pFFTOverlapBuf) - { - //major poblems if memory fails here - return; - } - m_InBufInPos = (CONV_FIR_SIZE - 1); - for( i=0; i= FHiCut) || - (FLoCut >= SampleRate/2.0) || - (FLoCut <= -SampleRate/2.0) || - (FHiCut >= SampleRate/2.0) || - (FHiCut <= -SampleRate/2.0) ) - { - return; - } - -#ifdef FMDSP_THREAD_SAFE - std::unique_lock lock(m_Mutex); -#endif - - //calculate some normalized filter parameters - TYPEREAL nFL = FLoCut/SampleRate; - TYPEREAL nFH = FHiCut/SampleRate; - TYPEREAL nFc = (nFH-nFL)/2.0; //prototype LP filter cutoff - TYPEREAL nFs = K_2PI*(nFH+nFL)/2.0; //2 PI times required frequency shift (FHiCut+FLoCut)/2 - TYPEREAL fCenter = 0.5*(TYPEREAL)(CONV_FIR_SIZE-1); //floating point center index of FIR filter - - for(i=0; i lock(m_Mutex); -#endif - - while(len--) - { - j = m_InBufInPos - (CONV_FFT_SIZE - CONV_FIR_SIZE + 1) ; - if(j >= 0 ) - { //keep copy of last CONV_FIR_SIZE-1 samples for overlap save - m_pFFTOverlapBuf[j] = InBuf[i]; - } - m_pFFTBuf[m_InBufInPos++] = InBuf[i++]; - if(m_InBufInPos >= CONV_FFT_SIZE) - { //perform FFT -> complexMultiply by FIR coefficients -> inverse FFT on filled FFT input buffer - m_Fft.FwdFFT(m_pFFTBuf); - CpxMpy(CONV_FFT_SIZE, m_pFilterCoef, m_pFFTBuf, m_pFFTBuf); - m_Fft.RevFFT(m_pFFTBuf); - for(j=(CONV_FIR_SIZE-1); j + + +////////////////////////////////////////////////////////////////////// +// Local Defines +////////////////////////////////////////////////////////////////////// +#define CONV_FFT_SIZE 2048 //must be power of 2 +#define CONV_FIR_SIZE 1025 //must be <= FFT size. Make 1/2 +1 if want + //output to be in power of 2 + + +#define CONV_INBUF_SIZE (CONV_FFT_SIZE+CONV_FIR_SIZE-1) + + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +CFastFIR::CFastFIR() +{ +int i; + m_pWindowTbl = NULL; + m_pFFTBuf = NULL; + m_pFFTOverlapBuf = NULL; + m_pFilterCoef = NULL; + //allocate internal buffer space on Heap + m_pWindowTbl = new TYPEREAL[CONV_FIR_SIZE]; + m_pFilterCoef = new TYPECPX[CONV_FFT_SIZE]; + m_pFFTBuf = new TYPECPX[CONV_FFT_SIZE]; + m_pFFTOverlapBuf = new TYPECPX[CONV_FIR_SIZE]; + + if(!m_pWindowTbl || !m_pFilterCoef || !m_pFFTBuf || !m_pFFTOverlapBuf) + { + //major poblems if memory fails here + return; + } + m_InBufInPos = (CONV_FIR_SIZE - 1); + for( i=0; i= FHiCut) || + (FLoCut >= SampleRate/2.0) || + (FLoCut <= -SampleRate/2.0) || + (FHiCut >= SampleRate/2.0) || + (FHiCut <= -SampleRate/2.0) ) + { + return; + } + +#ifdef FMDSP_THREAD_SAFE + std::unique_lock lock(m_Mutex); +#endif + + //calculate some normalized filter parameters + TYPEREAL nFL = FLoCut/SampleRate; + TYPEREAL nFH = FHiCut/SampleRate; + TYPEREAL nFc = (nFH-nFL)/2.0; //prototype LP filter cutoff + TYPEREAL nFs = K_2PI*(nFH+nFL)/2.0; //2 PI times required frequency shift (FHiCut+FLoCut)/2 + TYPEREAL fCenter = 0.5*(TYPEREAL)(CONV_FIR_SIZE-1); //floating point center index of FIR filter + + for(i=0; i lock(m_Mutex); +#endif + + while(len--) + { + j = m_InBufInPos - (CONV_FFT_SIZE - CONV_FIR_SIZE + 1) ; + if(j >= 0 ) + { //keep copy of last CONV_FIR_SIZE-1 samples for overlap save + m_pFFTOverlapBuf[j] = InBuf[i]; + } + m_pFFTBuf[m_InBufInPos++] = InBuf[i++]; + if(m_InBufInPos >= CONV_FFT_SIZE) + { //perform FFT -> complexMultiply by FIR coefficients -> inverse FFT on filled FFT input buffer + m_Fft.FwdFFT(m_pFFTBuf); + CpxMpy(CONV_FFT_SIZE, m_pFilterCoef, m_pFFTBuf, m_pFFTBuf); + m_Fft.RevFFT(m_pFFTBuf); + for(j=(CONV_FIR_SIZE-1); j - -class CFastFIR -{ -public: - CFastFIR(); - virtual ~CFastFIR(); - - void SetupParameters( TYPEREAL FLoCut,TYPEREAL FHiCut,TYPEREAL Offset, TYPEREAL SampleRate); - int ProcessData(int InLength, TYPECPX* InBuf, TYPECPX* OutBuf); - -private: - inline void CpxMpy(int N, TYPECPX* m, TYPECPX* src, TYPECPX* dest); - void FreeMemory(); - - TYPEREAL m_FLoCut; - TYPEREAL m_FHiCut; - TYPEREAL m_Offset; - TYPEREAL m_SampleRate; - - int m_InBufInPos; - TYPEREAL* m_pWindowTbl; - TYPECPX* m_pFFTOverlapBuf; - TYPECPX* m_pFilterCoef; - TYPECPX* m_pFFTBuf; -#ifdef FMDSP_THREAD_SAFE - mutable std::mutex m_Mutex; //for keeping threads from stomping on each other -#endif - CFft m_Fft; -}; -#endif // FASTFIR_H +////////////////////////////////////////////////////////////////////// +// FastFIR.h: interface for the CFastFIR class. +// +// This class implements a FIR Bandpass filter using a FFT convolution algorithm +//The filter is complex and is specified with 3 parameters: +// sample frequency, Hicut and Lowcut frequency +// +// History: +// 2010-09-15 Initial creation MSW +// 2011-03-27 Initial release +////////////////////////////////////////////////////////////////////// +//========================================================================================== +// + + + This Software is released under the "Simplified BSD License" + + + +//Copyright 2010 Moe Wheatley. All rights reserved. +// +//Redistribution and use in source and binary forms, with or without modification, are +//permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other materials +// provided with the distribution. +// +//THIS SOFTWARE IS PROVIDED BY Moe Wheatley ``AS IS'' AND ANY EXPRESS OR IMPLIED +//WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +//FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Moe Wheatley OR +//CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +//CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +//SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +//ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +//NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +//ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +//The views and conclusions contained in the software and documentation are those of the +//authors and should not be interpreted as representing official policies, either expressed +//or implied, of Moe Wheatley. +//============================================================================= +#ifndef FASTFIR_H +#define FASTFIR_H + +#include "datatypes.h" +#include "fft.h" + +#include + +class CFastFIR +{ +public: + CFastFIR(); + virtual ~CFastFIR(); + + void SetupParameters( TYPEREAL FLoCut,TYPEREAL FHiCut,TYPEREAL Offset, TYPEREAL SampleRate); + int ProcessData(int InLength, TYPECPX* InBuf, TYPECPX* OutBuf); + +private: + inline void CpxMpy(int N, TYPECPX* m, TYPECPX* src, TYPECPX* dest); + void FreeMemory(); + + TYPEREAL m_FLoCut; + TYPEREAL m_FHiCut; + TYPEREAL m_Offset; + TYPEREAL m_SampleRate; + + int m_InBufInPos; + TYPEREAL* m_pWindowTbl; + TYPECPX* m_pFFTOverlapBuf; + TYPECPX* m_pFilterCoef; + TYPECPX* m_pFFTBuf; +#ifdef FMDSP_THREAD_SAFE + mutable std::mutex m_Mutex; //for keeping threads from stomping on each other +#endif + CFft m_Fft; +}; +#endif // FASTFIR_H diff --git a/src/fmdsp/fft.cpp b/src/fmdsp/fft.cpp index f4a7dfb..1ac6cfd 100644 --- a/src/fmdsp/fft.cpp +++ b/src/fmdsp/fft.cpp @@ -1,1202 +1,1202 @@ -// fft.cpp: implementation of the CFft class. -// This is a somewhat modified version of Takuya OOURA's -// original radix 4 FFT package. -//Copyright(C) 1996-1998 Takuya OOURA -// (email: ooura@mmm.t.u-tokyo.ac.jp). -// -// History: -// 2010-09-15 Initial creation MSW -// 2011-03-27 Initial release -////////////////////////////////////////////////////////////////////// -#include -#include "fft.h" - -////////////////////////////////////////////////////////////////////// -// Local Defines -////////////////////////////////////////////////////////////////////// - -#define K_AMPMAX 32767.0 //maximum sin wave Pk for 16 bit input data -#define K_MAXDB 0.0 //specifies total range of FFT -#define K_MINDB -220.0 - -#define OVER_LIMIT 32000.0 //limit for detecting over ranging inputs - - -////////////////////////////////////////////////////////////////////// -// Construction/Destruction -////////////////////////////////////////////////////////////////////// -CFft::CFft() -{ - m_Overload = false; - m_Invert = false; - m_AveSize = 1; - m_LastFFTSize = 0; - m_AveCount = 0; - m_TotalCount = 0; - m_FFTSize = 1024; - m_pWorkArea = NULL; - m_pSinCosTbl = NULL; - m_pWindowTbl = NULL; - m_pFFTPwrAveBuf = NULL; - m_pFFTAveBuf = NULL; - m_pFFTInBuf = NULL; - m_pFFTSumBuf = NULL; - m_pTranslateTbl = NULL; - m_dBCompensation = K_MAXDB; - SetFFTParams( 2048, false ,0.0, 1000); - SetFFTAve( 1); -} - -CFft::~CFft() -{ // free all resources - FreeMemory(); -} - -void CFft::FreeMemory() -{ - if(m_pWorkArea) - { - delete m_pWorkArea; - m_pWorkArea = NULL; - } - if(m_pSinCosTbl) - { - delete m_pSinCosTbl; - m_pSinCosTbl = NULL; - } - if(m_pWindowTbl) - { - delete m_pWindowTbl; - m_pWindowTbl = NULL; - } - if(m_pFFTPwrAveBuf) - { - delete m_pFFTPwrAveBuf; - m_pFFTPwrAveBuf = NULL; - } - if(m_pFFTAveBuf) - { - delete m_pFFTAveBuf; - m_pFFTAveBuf = NULL; - } - if(m_pFFTSumBuf) - { - delete m_pFFTSumBuf; - m_pFFTSumBuf = NULL; - } - if(m_pFFTInBuf) - { - delete m_pFFTInBuf; - m_pFFTInBuf = NULL; - } - if(m_pTranslateTbl) - { - delete m_pTranslateTbl; - m_pTranslateTbl = NULL; - } -} - -/////////////////////////////////////////////////////////////////// -//FFT initialization and parameter setup function -/////////////////////////////////////////////////////////////////// -void CFft::SetFFTAve( qint32 ave) -{ - if(m_AveSize != ave) - { - if(ave>0) - m_AveSize = ave; - else - m_AveSize = 1; - } - ResetFFT(); -} - -/////////////////////////////////////////////////////////////////// -//FFT initialization and parameter setup function -/////////////////////////////////////////////////////////////////// -void CFft::SetFFTParams( qint32 size, - bool invert, - TYPEREAL dBCompensation, - TYPEREAL SampleFreq) -{ -qint32 i; - if(size==0) - return; - -#ifdef FMDSP_THREAD_SAFE - std::unique_lock lock(m_Mutex); -#endif - - m_BinMin = 0; //force recalculation of plot variables - m_BinMax = 0; - m_StartFreq = 0; - m_StopFreq = 0; - m_PlotWidth = 0; - m_Invert = invert; - m_SampleFreq = SampleFreq; - if( m_dBCompensation != dBCompensation ) - { //reset of FFT params - m_LastFFTSize = 0; - m_dBCompensation = dBCompensation; - } - - if( sizeMAX_FFT_SIZE ) - m_FFTSize = MAX_FFT_SIZE; - else - m_FFTSize = size; - - if(m_LastFFTSize != m_FFTSize) - { - m_LastFFTSize = m_FFTSize; - FreeMemory(); - m_pWindowTbl = new TYPEREAL[m_FFTSize]; - m_pSinCosTbl = new TYPEREAL[m_FFTSize/2]; - m_pWorkArea = new qint32[ (qint32)MSQRT((TYPEREAL)m_FFTSize)+2]; - m_pFFTPwrAveBuf = new TYPEREAL[m_FFTSize]; - m_pFFTAveBuf = new TYPEREAL[m_FFTSize]; - m_pFFTSumBuf = new TYPEREAL[m_FFTSize]; - for(i=0; i> K_C -// Then K_B = PdBmax - 20*log10( N*A/Kx ) -// K_C = 10 ^ ( (PdBmin-K_B)/10 ) -// for power range of 0 to 100 dB with input(A) of 32767 and N=262144 -// K_B = -86.63833 and K_C = 4.6114145e8 -// To eliminate the multiply by 10, divide by 10 so for an output -// range of 0 to -120dB the stored value is 0.0 to -12.0 -// so final constant K_B = -8.663833 -/////////////////////////////////////////////////////////////////////// - m_K_B = m_dBCompensation - 20*MLOG10( (TYPEREAL)m_FFTSize*K_AMPMAX/2.0 ); - m_K_C = MPOW( 10.0, (K_MINDB-m_K_B)/10.0 ); - m_K_B = m_K_B/10.0; -TYPEREAL WindowGain; -#if 0 - WindowGain = 1.0; - for(i=0; i lock(m_Mutex); -#endif - - for(qint32 i=0; i lock(m_Mutex); -#endif - - TYPEREAL dtmp1; - for(i=0; i OVER_LIMIT) || (InBuf[i].re > OVER_LIMIT)); - - dtmp1 = m_pWindowTbl[i]; - //NOTE: For some reason I and Q are swapped(demod I/Q does not apear to be swapped) - //possibly an issue with the FFT ? - ((TYPECPX*)m_pFFTInBuf)[i].im = dtmp1 * (InBuf[i].re);//window the I data - ((TYPECPX*)m_pFFTInBuf)[i].re = dtmp1 * (InBuf[i].im); //window the Q data - } - //Calculate the complex FFT - bitrv2(m_FFTSize*2, m_pWorkArea + 2, m_pFFTInBuf); - CpxFFT(m_FFTSize*2, m_pFFTInBuf, m_pSinCosTbl); - - return m_TotalCount; -} - -////////////////////////////////////////////////////////////////////// -// The bin range is "start" to "stop" Hz. -// The range of start to stop frequencies are mapped to the users -// plot screen size so the users buffer will be filled with an array -// of integers whos value is the pixel height and the index of the -// array is the x pixel coordinate. -// The function returns true if the input is overloaded -// This routine converts the data to 32 bit integers and is useful -// when displaying fft data on the screen. -// MaxHeight = Plot height in pixels(zero is top and increases down) -// MaxWidth = Plot width in pixels -// StartFreq = freq in Hz -// StopFreq = freq in Hz -// MaxdB = FFT dB level corresponding to output value == 0 -// must be <= to K_MAXDB -// MindB = FFT dB level corresponding to output value == MaxHeight -// must be >= to K_MINDB -////////////////////////////////////////////////////////////////////// -bool CFft::GetScreenIntegerFFTData(qint32 MaxHeight, - qint32 MaxWidth, - TYPEREAL MaxdB, - TYPEREAL MindB, - qint32 StartFreq, - qint32 StopFreq, - qint32* OutBuf ) -{ -qint32 i; -qint32 y; -qint32 x; -qint32 m; -qint32 ymax = -10000; -qint32 xprev = -1; -qint32 maxbin; -TYPEREAL dBmaxOffset = MaxdB/10.0; -TYPEREAL dBGainFactor = -10.0/(MaxdB-MindB); - -#ifdef FMDSP_THREAD_SAFE - std::unique_lock lock(m_Mutex); -#endif - - if( (m_StartFreq != StartFreq) || - (m_StopFreq != StopFreq) || - (m_PlotWidth != MaxWidth) ) - { //if something has changed need to redo translate table - m_StartFreq = StartFreq; - m_StopFreq = StopFreq; - m_PlotWidth = MaxWidth; - maxbin = m_FFTSize - 1; - m_BinMin = (qint32)((TYPEREAL)StartFreq*(TYPEREAL)m_FFTSize/m_SampleFreq); - m_BinMin += (m_FFTSize/2); - m_BinMax = (qint32)((TYPEREAL)StopFreq*(TYPEREAL)m_FFTSize/m_SampleFreq); - m_BinMax += (m_FFTSize/2); - if(m_BinMin < 0) //don't allow these go outside the translate table - m_BinMin = 0; - if(m_BinMin >= maxbin) - m_BinMin = maxbin; - if(m_BinMax < 0) - m_BinMax = 0; - if(m_BinMax >= maxbin) - m_BinMax = maxbin; - if( (m_BinMax-m_BinMin) > m_PlotWidth ) - { - //if more FFT points than plot points - for( i=m_BinMin; i<=m_BinMax; i++) - m_pTranslateTbl[i] = ( (i-m_BinMin)*m_PlotWidth )/(m_BinMax - m_BinMin); - } - else - { - //if more plot points than FFT points - for( i=0; i m_PlotWidth ) - { - //if more FFT points than plot points - for( i=m_BinMin; i<=m_BinMax; i++ ) - { - if(m_Invert) - y = (qint32)((TYPEREAL)MaxHeight*dBGainFactor*(m_pFFTAveBuf[(m-i)] - dBmaxOffset)); - else - y = (qint32)((TYPEREAL)MaxHeight*dBGainFactor*(m_pFFTAveBuf[i] - dBmaxOffset)); - if(y<0) - y = 0; - if(y > MaxHeight) - y = MaxHeight; - x = m_pTranslateTbl[i]; //get fft bin to plot x coordinate transform - if( x==xprev ) // still mappped to same fft bin coordinate - { - if(y < ymax) //store only the max value - { - OutBuf[x] = y; - ymax = y; - } - } - else - { - OutBuf[x] = y; - xprev = x; - ymax = y; - } - } - } - else - { - //if more plot points than FFT points - for( x=0; x MaxHeight) - y = MaxHeight; - OutBuf[x] = y; - } - } - - return m_Overload; -} - -/////////////////////////////////////////////////////////////////// -//Interface for doing fast convolution filters. Takes complex data -// in pInOutBuf and does fwd or rev FFT and places back in same buffer. -/////////////////////////////////////////////////////////////////// -void CFft::FwdFFT( TYPECPX* pInOutBuf) -{ - bitrv2(m_FFTSize*2, m_pWorkArea + 2, (TYPEREAL*)pInOutBuf); - CpxFFT(m_FFTSize*2, (TYPEREAL*)pInOutBuf, m_pSinCosTbl); -} - -void CFft::RevFFT( TYPECPX* pInOutBuf) -{ - bitrv2conj(m_FFTSize*2, m_pWorkArea + 2, (TYPEREAL*)pInOutBuf); - cftbsub(m_FFTSize*2, (TYPEREAL*)pInOutBuf, m_pSinCosTbl); -} - - -/////////////////////////////////////////////////////////////////// -// Nitty gritty fft routines by Takuya OOURA(Updated to his new version 4-18-02) -// Routine calculates real FFT -/////////////////////////////////////////////////////////////////// -void CFft::rftfsub(qint32 n, TYPEREAL *a, qint32 nc, TYPEREAL *c) -{ -qint32 j, k, kk, ks, m; -TYPEREAL wkr, wki, xr, xi, yr, yi; - - m_TotalCount++; - if(m_AveCount < m_AveSize) - m_AveCount++; - m = n >> 1; - ks = 2 * nc/m; - kk = 0; - for (j = 2; j < m; j += 2 ) - { - k = n - j; - kk += ks; - wkr = 0.5 - c[nc - kk]; - wki = c[kk]; - xr = a[j] - a[k]; - xi = a[j + 1] + a[k + 1]; - yr = wkr * xr - wki * xi; - yi = wkr * xi + wki * xr; - a[j] -= yr; - xi = a[j]*a[j]; - a[j+1] -= yi; - xi += ( a[j+1]*a[j+1]); - a[k] += yr; - xr = a[k]*a[k]; - a[k+1] -= yi; - xr += (a[k+1]*a[k+1]); - - //xr is real power xi is imag power terms - //perform moving average on power up to m_AveSize then do exponential averaging after that - if(m_TotalCount <= m_AveSize) - { - m_pFFTSumBuf[j] = m_pFFTSumBuf[j] + xi; - m_pFFTSumBuf[k] = m_pFFTSumBuf[k] + xr; - } - else - { - m_pFFTSumBuf[j] = m_pFFTSumBuf[j] - m_pFFTPwrAveBuf[j] + xi; - m_pFFTSumBuf[k] = m_pFFTSumBuf[k] - m_pFFTPwrAveBuf[k] + xr; - } - m_pFFTPwrAveBuf[j] = m_pFFTSumBuf[j]/(TYPEREAL)m_AveCount; - m_pFFTPwrAveBuf[k] = m_pFFTSumBuf[k]/(TYPEREAL)m_AveCount; - - m_pFFTAveBuf[j] = MLOG10(m_pFFTPwrAveBuf[j] + m_K_C) + m_K_B; - m_pFFTAveBuf[k] = MLOG10(m_pFFTPwrAveBuf[k] + m_K_C) + m_K_B; - - } - - a[0] *= a[0]; //calc DC term - xr = a[m]*a[m]+a[m+1]*a[m+1]; //calculate N/4(middle) term - - //xr is real power a[0] is imag power terms - //perform moving average on power up to m_AveSize then do exponential averaging after that - if(m_TotalCount <= m_AveSize) - { - m_pFFTSumBuf[0] = m_pFFTSumBuf[0] + a[0]; - m_pFFTSumBuf[n/2] = m_pFFTSumBuf[n/2] + xr; - } - else - { - m_pFFTSumBuf[0] = m_pFFTSumBuf[0] - m_pFFTPwrAveBuf[0] + a[0]; - m_pFFTSumBuf[n/2] = m_pFFTSumBuf[n/2] - m_pFFTPwrAveBuf[n/2] + xr; - } - m_pFFTPwrAveBuf[0] = m_pFFTSumBuf[0]/(TYPEREAL)m_AveCount; - m_pFFTPwrAveBuf[n/2] = m_pFFTSumBuf[n/2]/(TYPEREAL)m_AveCount; - - m_pFFTAveBuf[0] = MLOG10(m_pFFTPwrAveBuf[0] + m_K_C) + m_K_B; - m_pFFTAveBuf[n/2] = MLOG10(m_pFFTPwrAveBuf[n/2] + m_K_C) + m_K_B; - -} - - -/////////////////////////////////////////////////////////////////// -// Routine calculates complex FFT -/////////////////////////////////////////////////////////////////// -void CFft::CpxFFT(qint32 n, TYPEREAL *a, TYPEREAL *w) -{ -qint32 j, j1, j2, j3, l; -TYPEREAL x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; - - m_TotalCount++; - if(m_AveCount < m_AveSize) - m_AveCount++; - l = 2; - if (n > 8) { - cft1st(n, a, w); - l = 8; - while ((l << 2) < n) { - cftmdl(n, l, a, w); - l <<= 2; - } - } - if ((l << 2) == n) { - for (j = 0; j < l; j += 2) { - j1 = j + l; - j2 = j1 + l; - j3 = j2 + l; - x0r = a[j] + a[j1]; - x0i = a[j + 1] + a[j1 + 1]; - x1r = a[j] - a[j1]; - x1i = a[j + 1] - a[j1 + 1]; - x2r = a[j2] + a[j3]; - x2i = a[j2 + 1] + a[j3 + 1]; - x3r = a[j2] - a[j3]; - x3i = a[j2 + 1] - a[j3 + 1]; - a[j] = x0r + x2r; - a[j + 1] = x0i + x2i; - a[j2] = x0r - x2r; - a[j2 + 1] = x0i - x2i; - a[j1] = x1r - x3i; - a[j1 + 1] = x1i + x3r; - a[j3] = x1r + x3i; - a[j3 + 1] = x1i - x3r; - } - } else { - for (j = 0; j < l; j += 2) { - j1 = j + l; - x0r = a[j] - a[j1]; - x0i = a[j + 1] - a[j1 + 1]; - a[j] += a[j1]; - a[j + 1] += a[j1 + 1]; - a[j1] = x0r; - a[j1 + 1] = x0i; - } - } - //n = 2*FFTSIZE - n = n>>1; - //now n = FFTSIZE - - // FFT output index 0 to N/2-1 - // is frequency output 0 to +Fs/2 Hz ( 0 Hz DC term ) - for( l=0,j=n/2; j 2) { - nwh = nw >> 1; - delta = MATAN(1.0) / nwh; - w[0] = 1; - w[1] = 0; - w[nwh] = MCOS(delta * nwh); - w[nwh + 1] = w[nwh]; - if (nwh > 2) { - for (j = 2; j < nwh; j += 2) { - x = MCOS(delta * j); - y = MSIN(delta * j); - w[j] = x; - w[j + 1] = y; - w[nw - j] = y; - w[nw - j + 1] = x; - } - bitrv2(nw, ip + 2, w); - } - } -} - -/////////////////////////////////////////////////////////////////// -void CFft::makect(qint32 nc, qint32 *ip, TYPEREAL *c) -{ -qint32 j, nch; -TYPEREAL delta; - - ip[1] = nc; - if (nc > 1) { - nch = nc >> 1; - delta = MATAN(1.0) / nch; - c[0] = MCOS(delta * nch); - c[nch] = 0.5 * c[0]; - for (j = 1; j < nch; j++) { - c[j] = 0.5 * MCOS(delta * j); - c[nc - j] = 0.5 * MSIN(delta * j); - } - } -} - -/////////////////////////////////////////////////////////////////// -/* -------- child routines -------- */ -/////////////////////////////////////////////////////////////////// -void CFft::bitrv2(qint32 n, qint32 *ip, TYPEREAL *a) -{ -qint32 j, j1, k, k1, l, m, m2; -TYPEREAL xr, xi, yr, yi; - - ip[0] = 0; - l = n; - m = 1; - while ((m << 3) < l) { - l >>= 1; - for (j = 0; j < m; j++) { - ip[m + j] = ip[j] + l; - } - m <<= 1; - } - m2 = 2 * m; - if ((m << 3) == l) { - for (k = 0; k < m; k++) { - for (j = 0; j < k; j++) { - j1 = 2 * j + ip[k]; - k1 = 2 * k + ip[j]; - xr = a[j1]; - xi = a[j1 + 1]; - yr = a[k1]; - yi = a[k1 + 1]; - a[j1] = yr; - a[j1 + 1] = yi; - a[k1] = xr; - a[k1 + 1] = xi; - j1 += m2; - k1 += 2 * m2; - xr = a[j1]; - xi = a[j1 + 1]; - yr = a[k1]; - yi = a[k1 + 1]; - a[j1] = yr; - a[j1 + 1] = yi; - a[k1] = xr; - a[k1 + 1] = xi; - j1 += m2; - k1 -= m2; - xr = a[j1]; - xi = a[j1 + 1]; - yr = a[k1]; - yi = a[k1 + 1]; - a[j1] = yr; - a[j1 + 1] = yi; - a[k1] = xr; - a[k1 + 1] = xi; - j1 += m2; - k1 += 2 * m2; - xr = a[j1]; - xi = a[j1 + 1]; - yr = a[k1]; - yi = a[k1 + 1]; - a[j1] = yr; - a[j1 + 1] = yi; - a[k1] = xr; - a[k1 + 1] = xi; - } - j1 = 2 * k + m2 + ip[k]; - k1 = j1 + m2; - xr = a[j1]; - xi = a[j1 + 1]; - yr = a[k1]; - yi = a[k1 + 1]; - a[j1] = yr; - a[j1 + 1] = yi; - a[k1] = xr; - a[k1 + 1] = xi; - } - } else { - for (k = 1; k < m; k++) { - for (j = 0; j < k; j++) { - j1 = 2 * j + ip[k]; - k1 = 2 * k + ip[j]; - xr = a[j1]; - xi = a[j1 + 1]; - yr = a[k1]; - yi = a[k1 + 1]; - a[j1] = yr; - a[j1 + 1] = yi; - a[k1] = xr; - a[k1 + 1] = xi; - j1 += m2; - k1 += m2; - xr = a[j1]; - xi = a[j1 + 1]; - yr = a[k1]; - yi = a[k1 + 1]; - a[j1] = yr; - a[j1 + 1] = yi; - a[k1] = xr; - a[k1 + 1] = xi; - } - } - } -} - -/////////////////////////////////////////////////////////////////// -void CFft::cftfsub(qint32 n, TYPEREAL *a, TYPEREAL *w) -{ -qint32 j, j1, j2, j3, l; -TYPEREAL x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; - - l = 2; - if (n > 8) { - cft1st(n, a, w); - l = 8; - while ((l << 2) < n) { - cftmdl(n, l, a, w); - l <<= 2; - } - } - if ((l << 2) == n) { - for (j = 0; j < l; j += 2) { - j1 = j + l; - j2 = j1 + l; - j3 = j2 + l; - x0r = a[j] + a[j1]; - x0i = a[j + 1] + a[j1 + 1]; - x1r = a[j] - a[j1]; - x1i = a[j + 1] - a[j1 + 1]; - x2r = a[j2] + a[j3]; - x2i = a[j2 + 1] + a[j3 + 1]; - x3r = a[j2] - a[j3]; - x3i = a[j2 + 1] - a[j3 + 1]; - a[j] = x0r + x2r; - a[j + 1] = x0i + x2i; - a[j2] = x0r - x2r; - a[j2 + 1] = x0i - x2i; - a[j1] = x1r - x3i; - a[j1 + 1] = x1i + x3r; - a[j3] = x1r + x3i; - a[j3 + 1] = x1i - x3r; - } - } else { - for (j = 0; j < l; j += 2) { - j1 = j + l; - x0r = a[j] - a[j1]; - x0i = a[j + 1] - a[j1 + 1]; - a[j] += a[j1]; - a[j + 1] += a[j1 + 1]; - a[j1] = x0r; - a[j1 + 1] = x0i; - } - } -} - -/////////////////////////////////////////////////////////////////// -void CFft::cft1st(qint32 n, TYPEREAL *a, TYPEREAL *w) -{ -qint32 j, k1, k2; -TYPEREAL wk1r, wk1i, wk2r, wk2i, wk3r, wk3i; -TYPEREAL x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; - - x0r = a[0] + a[2]; - x0i = a[1] + a[3]; - x1r = a[0] - a[2]; - x1i = a[1] - a[3]; - x2r = a[4] + a[6]; - x2i = a[5] + a[7]; - x3r = a[4] - a[6]; - x3i = a[5] - a[7]; - a[0] = x0r + x2r; - a[1] = x0i + x2i; - a[4] = x0r - x2r; - a[5] = x0i - x2i; - a[2] = x1r - x3i; - a[3] = x1i + x3r; - a[6] = x1r + x3i; - a[7] = x1i - x3r; - wk1r = w[2]; - x0r = a[8] + a[10]; - x0i = a[9] + a[11]; - x1r = a[8] - a[10]; - x1i = a[9] - a[11]; - x2r = a[12] + a[14]; - x2i = a[13] + a[15]; - x3r = a[12] - a[14]; - x3i = a[13] - a[15]; - a[8] = x0r + x2r; - a[9] = x0i + x2i; - a[12] = x2i - x0i; - a[13] = x0r - x2r; - x0r = x1r - x3i; - x0i = x1i + x3r; - a[10] = wk1r * (x0r - x0i); - a[11] = wk1r * (x0r + x0i); - x0r = x3i + x1r; - x0i = x3r - x1i; - a[14] = wk1r * (x0i - x0r); - a[15] = wk1r * (x0i + x0r); - k1 = 0; - for (j = 16; j < n; j += 16) { - k1 += 2; - k2 = 2 * k1; - wk2r = w[k1]; - wk2i = w[k1 + 1]; - wk1r = w[k2]; - wk1i = w[k2 + 1]; - wk3r = wk1r - 2 * wk2i * wk1i; - wk3i = 2 * wk2i * wk1r - wk1i; - x0r = a[j] + a[j + 2]; - x0i = a[j + 1] + a[j + 3]; - x1r = a[j] - a[j + 2]; - x1i = a[j + 1] - a[j + 3]; - x2r = a[j + 4] + a[j + 6]; - x2i = a[j + 5] + a[j + 7]; - x3r = a[j + 4] - a[j + 6]; - x3i = a[j + 5] - a[j + 7]; - a[j] = x0r + x2r; - a[j + 1] = x0i + x2i; - x0r -= x2r; - x0i -= x2i; - a[j + 4] = wk2r * x0r - wk2i * x0i; - a[j + 5] = wk2r * x0i + wk2i * x0r; - x0r = x1r - x3i; - x0i = x1i + x3r; - a[j + 2] = wk1r * x0r - wk1i * x0i; - a[j + 3] = wk1r * x0i + wk1i * x0r; - x0r = x1r + x3i; - x0i = x1i - x3r; - a[j + 6] = wk3r * x0r - wk3i * x0i; - a[j + 7] = wk3r * x0i + wk3i * x0r; - wk1r = w[k2 + 2]; - wk1i = w[k2 + 3]; - wk3r = wk1r - 2 * wk2r * wk1i; - wk3i = 2 * wk2r * wk1r - wk1i; - x0r = a[j + 8] + a[j + 10]; - x0i = a[j + 9] + a[j + 11]; - x1r = a[j + 8] - a[j + 10]; - x1i = a[j + 9] - a[j + 11]; - x2r = a[j + 12] + a[j + 14]; - x2i = a[j + 13] + a[j + 15]; - x3r = a[j + 12] - a[j + 14]; - x3i = a[j + 13] - a[j + 15]; - a[j + 8] = x0r + x2r; - a[j + 9] = x0i + x2i; - x0r -= x2r; - x0i -= x2i; - a[j + 12] = -wk2i * x0r - wk2r * x0i; - a[j + 13] = -wk2i * x0i + wk2r * x0r; - x0r = x1r - x3i; - x0i = x1i + x3r; - a[j + 10] = wk1r * x0r - wk1i * x0i; - a[j + 11] = wk1r * x0i + wk1i * x0r; - x0r = x1r + x3i; - x0i = x1i - x3r; - a[j + 14] = wk3r * x0r - wk3i * x0i; - a[j + 15] = wk3r * x0i + wk3i * x0r; - } -} - -/////////////////////////////////////////////////////////////////// -void CFft::cftmdl(qint32 n, qint32 l, TYPEREAL *a, TYPEREAL *w) -{ -qint32 j, j1, j2, j3, k, k1, k2, m, m2; -TYPEREAL wk1r, wk1i, wk2r, wk2i, wk3r, wk3i; -TYPEREAL x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; - - m = l << 2; - for (j = 0; j < l; j += 2) { - j1 = j + l; - j2 = j1 + l; - j3 = j2 + l; - x0r = a[j] + a[j1]; - x0i = a[j + 1] + a[j1 + 1]; - x1r = a[j] - a[j1]; - x1i = a[j + 1] - a[j1 + 1]; - x2r = a[j2] + a[j3]; - x2i = a[j2 + 1] + a[j3 + 1]; - x3r = a[j2] - a[j3]; - x3i = a[j2 + 1] - a[j3 + 1]; - a[j] = x0r + x2r; - a[j + 1] = x0i + x2i; - a[j2] = x0r - x2r; - a[j2 + 1] = x0i - x2i; - a[j1] = x1r - x3i; - a[j1 + 1] = x1i + x3r; - a[j3] = x1r + x3i; - a[j3 + 1] = x1i - x3r; - } - wk1r = w[2]; - for (j = m; j < l + m; j += 2) { - j1 = j + l; - j2 = j1 + l; - j3 = j2 + l; - x0r = a[j] + a[j1]; - x0i = a[j + 1] + a[j1 + 1]; - x1r = a[j] - a[j1]; - x1i = a[j + 1] - a[j1 + 1]; - x2r = a[j2] + a[j3]; - x2i = a[j2 + 1] + a[j3 + 1]; - x3r = a[j2] - a[j3]; - x3i = a[j2 + 1] - a[j3 + 1]; - a[j] = x0r + x2r; - a[j + 1] = x0i + x2i; - a[j2] = x2i - x0i; - a[j2 + 1] = x0r - x2r; - x0r = x1r - x3i; - x0i = x1i + x3r; - a[j1] = wk1r * (x0r - x0i); - a[j1 + 1] = wk1r * (x0r + x0i); - x0r = x3i + x1r; - x0i = x3r - x1i; - a[j3] = wk1r * (x0i - x0r); - a[j3 + 1] = wk1r * (x0i + x0r); - } - k1 = 0; - m2 = 2 * m; - for (k = m2; k < n; k += m2) { - k1 += 2; - k2 = 2 * k1; - wk2r = w[k1]; - wk2i = w[k1 + 1]; - wk1r = w[k2]; - wk1i = w[k2 + 1]; - wk3r = wk1r - 2 * wk2i * wk1i; - wk3i = 2 * wk2i * wk1r - wk1i; - for (j = k; j < l + k; j += 2) { - j1 = j + l; - j2 = j1 + l; - j3 = j2 + l; - x0r = a[j] + a[j1]; - x0i = a[j + 1] + a[j1 + 1]; - x1r = a[j] - a[j1]; - x1i = a[j + 1] - a[j1 + 1]; - x2r = a[j2] + a[j3]; - x2i = a[j2 + 1] + a[j3 + 1]; - x3r = a[j2] - a[j3]; - x3i = a[j2 + 1] - a[j3 + 1]; - a[j] = x0r + x2r; - a[j + 1] = x0i + x2i; - x0r -= x2r; - x0i -= x2i; - a[j2] = wk2r * x0r - wk2i * x0i; - a[j2 + 1] = wk2r * x0i + wk2i * x0r; - x0r = x1r - x3i; - x0i = x1i + x3r; - a[j1] = wk1r * x0r - wk1i * x0i; - a[j1 + 1] = wk1r * x0i + wk1i * x0r; - x0r = x1r + x3i; - x0i = x1i - x3r; - a[j3] = wk3r * x0r - wk3i * x0i; - a[j3 + 1] = wk3r * x0i + wk3i * x0r; - } - wk1r = w[k2 + 2]; - wk1i = w[k2 + 3]; - wk3r = wk1r - 2 * wk2r * wk1i; - wk3i = 2 * wk2r * wk1r - wk1i; - for (j = k + m; j < l + (k + m); j += 2) { - j1 = j + l; - j2 = j1 + l; - j3 = j2 + l; - x0r = a[j] + a[j1]; - x0i = a[j + 1] + a[j1 + 1]; - x1r = a[j] - a[j1]; - x1i = a[j + 1] - a[j1 + 1]; - x2r = a[j2] + a[j3]; - x2i = a[j2 + 1] + a[j3 + 1]; - x3r = a[j2] - a[j3]; - x3i = a[j2 + 1] - a[j3 + 1]; - a[j] = x0r + x2r; - a[j + 1] = x0i + x2i; - x0r -= x2r; - x0i -= x2i; - a[j2] = -wk2i * x0r - wk2r * x0i; - a[j2 + 1] = -wk2i * x0i + wk2r * x0r; - x0r = x1r - x3i; - x0i = x1i + x3r; - a[j1] = wk1r * x0r - wk1i * x0i; - a[j1 + 1] = wk1r * x0i + wk1i * x0r; - x0r = x1r + x3i; - x0i = x1i - x3r; - a[j3] = wk3r * x0r - wk3i * x0i; - a[j3 + 1] = wk3r * x0i + wk3i * x0r; - } - } -} - -void CFft::bitrv2conj(int n, int *ip, TYPEREAL *a) -{ - int j, j1, k, k1, l, m, m2; - TYPEREAL xr, xi, yr, yi; - - ip[0] = 0; - l = n; - m = 1; - while ((m << 3) < l) { - l >>= 1; - for (j = 0; j < m; j++) { - ip[m + j] = ip[j] + l; - } - m <<= 1; - } - m2 = 2 * m; - if ((m << 3) == l) { - for (k = 0; k < m; k++) { - for (j = 0; j < k; j++) { - j1 = 2 * j + ip[k]; - k1 = 2 * k + ip[j]; - xr = a[j1]; - xi = -a[j1 + 1]; - yr = a[k1]; - yi = -a[k1 + 1]; - a[j1] = yr; - a[j1 + 1] = yi; - a[k1] = xr; - a[k1 + 1] = xi; - j1 += m2; - k1 += 2 * m2; - xr = a[j1]; - xi = -a[j1 + 1]; - yr = a[k1]; - yi = -a[k1 + 1]; - a[j1] = yr; - a[j1 + 1] = yi; - a[k1] = xr; - a[k1 + 1] = xi; - j1 += m2; - k1 -= m2; - xr = a[j1]; - xi = -a[j1 + 1]; - yr = a[k1]; - yi = -a[k1 + 1]; - a[j1] = yr; - a[j1 + 1] = yi; - a[k1] = xr; - a[k1 + 1] = xi; - j1 += m2; - k1 += 2 * m2; - xr = a[j1]; - xi = -a[j1 + 1]; - yr = a[k1]; - yi = -a[k1 + 1]; - a[j1] = yr; - a[j1 + 1] = yi; - a[k1] = xr; - a[k1 + 1] = xi; - } - k1 = 2 * k + ip[k]; - a[k1 + 1] = -a[k1 + 1]; - j1 = k1 + m2; - k1 = j1 + m2; - xr = a[j1]; - xi = -a[j1 + 1]; - yr = a[k1]; - yi = -a[k1 + 1]; - a[j1] = yr; - a[j1 + 1] = yi; - a[k1] = xr; - a[k1 + 1] = xi; - k1 += m2; - a[k1 + 1] = -a[k1 + 1]; - } - } else { - a[1] = -a[1]; - a[m2 + 1] = -a[m2 + 1]; - for (k = 1; k < m; k++) { - for (j = 0; j < k; j++) { - j1 = 2 * j + ip[k]; - k1 = 2 * k + ip[j]; - xr = a[j1]; - xi = -a[j1 + 1]; - yr = a[k1]; - yi = -a[k1 + 1]; - a[j1] = yr; - a[j1 + 1] = yi; - a[k1] = xr; - a[k1 + 1] = xi; - j1 += m2; - k1 += m2; - xr = a[j1]; - xi = -a[j1 + 1]; - yr = a[k1]; - yi = -a[k1 + 1]; - a[j1] = yr; - a[j1 + 1] = yi; - a[k1] = xr; - a[k1 + 1] = xi; - } - k1 = 2 * k + ip[k]; - a[k1 + 1] = -a[k1 + 1]; - a[k1 + m2 + 1] = -a[k1 + m2 + 1]; - } - } -} - -void CFft::cftbsub(int n, TYPEREAL *a, TYPEREAL *w) -{ - int j, j1, j2, j3, l; - TYPEREAL x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; - - l = 2; - if (n > 8) { - cft1st(n, a, w); - l = 8; - while ((l << 2) < n) { - cftmdl(n, l, a, w); - l <<= 2; - } - } - if ((l << 2) == n) { - for (j = 0; j < l; j += 2) { - j1 = j + l; - j2 = j1 + l; - j3 = j2 + l; - x0r = a[j] + a[j1]; - x0i = -a[j + 1] - a[j1 + 1]; - x1r = a[j] - a[j1]; - x1i = -a[j + 1] + a[j1 + 1]; - x2r = a[j2] + a[j3]; - x2i = a[j2 + 1] + a[j3 + 1]; - x3r = a[j2] - a[j3]; - x3i = a[j2 + 1] - a[j3 + 1]; - a[j] = x0r + x2r; - a[j + 1] = x0i - x2i; - a[j2] = x0r - x2r; - a[j2 + 1] = x0i + x2i; - a[j1] = x1r - x3i; - a[j1 + 1] = x1i - x3r; - a[j3] = x1r + x3i; - a[j3 + 1] = x1i + x3r; - } - } else { - for (j = 0; j < l; j += 2) { - j1 = j + l; - x0r = a[j] - a[j1]; - x0i = -a[j + 1] + a[j1 + 1]; - a[j] += a[j1]; - a[j + 1] = -a[j + 1] - a[j1 + 1]; - a[j1] = x0r; - a[j1 + 1] = x0i; - } - } -} - - +// fft.cpp: implementation of the CFft class. +// This is a somewhat modified version of Takuya OOURA's +// original radix 4 FFT package. +//Copyright(C) 1996-1998 Takuya OOURA +// (email: ooura@mmm.t.u-tokyo.ac.jp). +// +// History: +// 2010-09-15 Initial creation MSW +// 2011-03-27 Initial release +////////////////////////////////////////////////////////////////////// +#include +#include "fft.h" + +////////////////////////////////////////////////////////////////////// +// Local Defines +////////////////////////////////////////////////////////////////////// + +#define K_AMPMAX 32767.0 //maximum sin wave Pk for 16 bit input data +#define K_MAXDB 0.0 //specifies total range of FFT +#define K_MINDB -220.0 + +#define OVER_LIMIT 32000.0 //limit for detecting over ranging inputs + + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// +CFft::CFft() +{ + m_Overload = false; + m_Invert = false; + m_AveSize = 1; + m_LastFFTSize = 0; + m_AveCount = 0; + m_TotalCount = 0; + m_FFTSize = 1024; + m_pWorkArea = NULL; + m_pSinCosTbl = NULL; + m_pWindowTbl = NULL; + m_pFFTPwrAveBuf = NULL; + m_pFFTAveBuf = NULL; + m_pFFTInBuf = NULL; + m_pFFTSumBuf = NULL; + m_pTranslateTbl = NULL; + m_dBCompensation = K_MAXDB; + SetFFTParams( 2048, false ,0.0, 1000); + SetFFTAve( 1); +} + +CFft::~CFft() +{ // free all resources + FreeMemory(); +} + +void CFft::FreeMemory() +{ + if(m_pWorkArea) + { + delete m_pWorkArea; + m_pWorkArea = NULL; + } + if(m_pSinCosTbl) + { + delete m_pSinCosTbl; + m_pSinCosTbl = NULL; + } + if(m_pWindowTbl) + { + delete m_pWindowTbl; + m_pWindowTbl = NULL; + } + if(m_pFFTPwrAveBuf) + { + delete m_pFFTPwrAveBuf; + m_pFFTPwrAveBuf = NULL; + } + if(m_pFFTAveBuf) + { + delete m_pFFTAveBuf; + m_pFFTAveBuf = NULL; + } + if(m_pFFTSumBuf) + { + delete m_pFFTSumBuf; + m_pFFTSumBuf = NULL; + } + if(m_pFFTInBuf) + { + delete m_pFFTInBuf; + m_pFFTInBuf = NULL; + } + if(m_pTranslateTbl) + { + delete m_pTranslateTbl; + m_pTranslateTbl = NULL; + } +} + +/////////////////////////////////////////////////////////////////// +//FFT initialization and parameter setup function +/////////////////////////////////////////////////////////////////// +void CFft::SetFFTAve( qint32 ave) +{ + if(m_AveSize != ave) + { + if(ave>0) + m_AveSize = ave; + else + m_AveSize = 1; + } + ResetFFT(); +} + +/////////////////////////////////////////////////////////////////// +//FFT initialization and parameter setup function +/////////////////////////////////////////////////////////////////// +void CFft::SetFFTParams( qint32 size, + bool invert, + TYPEREAL dBCompensation, + TYPEREAL SampleFreq) +{ +qint32 i; + if(size==0) + return; + +#ifdef FMDSP_THREAD_SAFE + std::unique_lock lock(m_Mutex); +#endif + + m_BinMin = 0; //force recalculation of plot variables + m_BinMax = 0; + m_StartFreq = 0; + m_StopFreq = 0; + m_PlotWidth = 0; + m_Invert = invert; + m_SampleFreq = SampleFreq; + if( m_dBCompensation != dBCompensation ) + { //reset of FFT params + m_LastFFTSize = 0; + m_dBCompensation = dBCompensation; + } + + if( sizeMAX_FFT_SIZE ) + m_FFTSize = MAX_FFT_SIZE; + else + m_FFTSize = size; + + if(m_LastFFTSize != m_FFTSize) + { + m_LastFFTSize = m_FFTSize; + FreeMemory(); + m_pWindowTbl = new TYPEREAL[m_FFTSize]; + m_pSinCosTbl = new TYPEREAL[m_FFTSize/2]; + m_pWorkArea = new qint32[ (qint32)MSQRT((TYPEREAL)m_FFTSize)+2]; + m_pFFTPwrAveBuf = new TYPEREAL[m_FFTSize]; + m_pFFTAveBuf = new TYPEREAL[m_FFTSize]; + m_pFFTSumBuf = new TYPEREAL[m_FFTSize]; + for(i=0; i> K_C +// Then K_B = PdBmax - 20*log10( N*A/Kx ) +// K_C = 10 ^ ( (PdBmin-K_B)/10 ) +// for power range of 0 to 100 dB with input(A) of 32767 and N=262144 +// K_B = -86.63833 and K_C = 4.6114145e8 +// To eliminate the multiply by 10, divide by 10 so for an output +// range of 0 to -120dB the stored value is 0.0 to -12.0 +// so final constant K_B = -8.663833 +/////////////////////////////////////////////////////////////////////// + m_K_B = m_dBCompensation - 20*MLOG10( (TYPEREAL)m_FFTSize*K_AMPMAX/2.0 ); + m_K_C = MPOW( 10.0, (K_MINDB-m_K_B)/10.0 ); + m_K_B = m_K_B/10.0; +TYPEREAL WindowGain; +#if 0 + WindowGain = 1.0; + for(i=0; i lock(m_Mutex); +#endif + + for(qint32 i=0; i lock(m_Mutex); +#endif + + TYPEREAL dtmp1; + for(i=0; i OVER_LIMIT) || (InBuf[i].re > OVER_LIMIT)); + + dtmp1 = m_pWindowTbl[i]; + //NOTE: For some reason I and Q are swapped(demod I/Q does not apear to be swapped) + //possibly an issue with the FFT ? + ((TYPECPX*)m_pFFTInBuf)[i].im = dtmp1 * (InBuf[i].re);//window the I data + ((TYPECPX*)m_pFFTInBuf)[i].re = dtmp1 * (InBuf[i].im); //window the Q data + } + //Calculate the complex FFT + bitrv2(m_FFTSize*2, m_pWorkArea + 2, m_pFFTInBuf); + CpxFFT(m_FFTSize*2, m_pFFTInBuf, m_pSinCosTbl); + + return m_TotalCount; +} + +////////////////////////////////////////////////////////////////////// +// The bin range is "start" to "stop" Hz. +// The range of start to stop frequencies are mapped to the users +// plot screen size so the users buffer will be filled with an array +// of integers whos value is the pixel height and the index of the +// array is the x pixel coordinate. +// The function returns true if the input is overloaded +// This routine converts the data to 32 bit integers and is useful +// when displaying fft data on the screen. +// MaxHeight = Plot height in pixels(zero is top and increases down) +// MaxWidth = Plot width in pixels +// StartFreq = freq in Hz +// StopFreq = freq in Hz +// MaxdB = FFT dB level corresponding to output value == 0 +// must be <= to K_MAXDB +// MindB = FFT dB level corresponding to output value == MaxHeight +// must be >= to K_MINDB +////////////////////////////////////////////////////////////////////// +bool CFft::GetScreenIntegerFFTData(qint32 MaxHeight, + qint32 MaxWidth, + TYPEREAL MaxdB, + TYPEREAL MindB, + qint32 StartFreq, + qint32 StopFreq, + qint32* OutBuf ) +{ +qint32 i; +qint32 y; +qint32 x; +qint32 m; +qint32 ymax = -10000; +qint32 xprev = -1; +qint32 maxbin; +TYPEREAL dBmaxOffset = MaxdB/10.0; +TYPEREAL dBGainFactor = -10.0/(MaxdB-MindB); + +#ifdef FMDSP_THREAD_SAFE + std::unique_lock lock(m_Mutex); +#endif + + if( (m_StartFreq != StartFreq) || + (m_StopFreq != StopFreq) || + (m_PlotWidth != MaxWidth) ) + { //if something has changed need to redo translate table + m_StartFreq = StartFreq; + m_StopFreq = StopFreq; + m_PlotWidth = MaxWidth; + maxbin = m_FFTSize - 1; + m_BinMin = (qint32)((TYPEREAL)StartFreq*(TYPEREAL)m_FFTSize/m_SampleFreq); + m_BinMin += (m_FFTSize/2); + m_BinMax = (qint32)((TYPEREAL)StopFreq*(TYPEREAL)m_FFTSize/m_SampleFreq); + m_BinMax += (m_FFTSize/2); + if(m_BinMin < 0) //don't allow these go outside the translate table + m_BinMin = 0; + if(m_BinMin >= maxbin) + m_BinMin = maxbin; + if(m_BinMax < 0) + m_BinMax = 0; + if(m_BinMax >= maxbin) + m_BinMax = maxbin; + if( (m_BinMax-m_BinMin) > m_PlotWidth ) + { + //if more FFT points than plot points + for( i=m_BinMin; i<=m_BinMax; i++) + m_pTranslateTbl[i] = ( (i-m_BinMin)*m_PlotWidth )/(m_BinMax - m_BinMin); + } + else + { + //if more plot points than FFT points + for( i=0; i m_PlotWidth ) + { + //if more FFT points than plot points + for( i=m_BinMin; i<=m_BinMax; i++ ) + { + if(m_Invert) + y = (qint32)((TYPEREAL)MaxHeight*dBGainFactor*(m_pFFTAveBuf[(m-i)] - dBmaxOffset)); + else + y = (qint32)((TYPEREAL)MaxHeight*dBGainFactor*(m_pFFTAveBuf[i] - dBmaxOffset)); + if(y<0) + y = 0; + if(y > MaxHeight) + y = MaxHeight; + x = m_pTranslateTbl[i]; //get fft bin to plot x coordinate transform + if( x==xprev ) // still mappped to same fft bin coordinate + { + if(y < ymax) //store only the max value + { + OutBuf[x] = y; + ymax = y; + } + } + else + { + OutBuf[x] = y; + xprev = x; + ymax = y; + } + } + } + else + { + //if more plot points than FFT points + for( x=0; x MaxHeight) + y = MaxHeight; + OutBuf[x] = y; + } + } + + return m_Overload; +} + +/////////////////////////////////////////////////////////////////// +//Interface for doing fast convolution filters. Takes complex data +// in pInOutBuf and does fwd or rev FFT and places back in same buffer. +/////////////////////////////////////////////////////////////////// +void CFft::FwdFFT( TYPECPX* pInOutBuf) +{ + bitrv2(m_FFTSize*2, m_pWorkArea + 2, (TYPEREAL*)pInOutBuf); + CpxFFT(m_FFTSize*2, (TYPEREAL*)pInOutBuf, m_pSinCosTbl); +} + +void CFft::RevFFT( TYPECPX* pInOutBuf) +{ + bitrv2conj(m_FFTSize*2, m_pWorkArea + 2, (TYPEREAL*)pInOutBuf); + cftbsub(m_FFTSize*2, (TYPEREAL*)pInOutBuf, m_pSinCosTbl); +} + + +/////////////////////////////////////////////////////////////////// +// Nitty gritty fft routines by Takuya OOURA(Updated to his new version 4-18-02) +// Routine calculates real FFT +/////////////////////////////////////////////////////////////////// +void CFft::rftfsub(qint32 n, TYPEREAL *a, qint32 nc, TYPEREAL *c) +{ +qint32 j, k, kk, ks, m; +TYPEREAL wkr, wki, xr, xi, yr, yi; + + m_TotalCount++; + if(m_AveCount < m_AveSize) + m_AveCount++; + m = n >> 1; + ks = 2 * nc/m; + kk = 0; + for (j = 2; j < m; j += 2 ) + { + k = n - j; + kk += ks; + wkr = 0.5 - c[nc - kk]; + wki = c[kk]; + xr = a[j] - a[k]; + xi = a[j + 1] + a[k + 1]; + yr = wkr * xr - wki * xi; + yi = wkr * xi + wki * xr; + a[j] -= yr; + xi = a[j]*a[j]; + a[j+1] -= yi; + xi += ( a[j+1]*a[j+1]); + a[k] += yr; + xr = a[k]*a[k]; + a[k+1] -= yi; + xr += (a[k+1]*a[k+1]); + + //xr is real power xi is imag power terms + //perform moving average on power up to m_AveSize then do exponential averaging after that + if(m_TotalCount <= m_AveSize) + { + m_pFFTSumBuf[j] = m_pFFTSumBuf[j] + xi; + m_pFFTSumBuf[k] = m_pFFTSumBuf[k] + xr; + } + else + { + m_pFFTSumBuf[j] = m_pFFTSumBuf[j] - m_pFFTPwrAveBuf[j] + xi; + m_pFFTSumBuf[k] = m_pFFTSumBuf[k] - m_pFFTPwrAveBuf[k] + xr; + } + m_pFFTPwrAveBuf[j] = m_pFFTSumBuf[j]/(TYPEREAL)m_AveCount; + m_pFFTPwrAveBuf[k] = m_pFFTSumBuf[k]/(TYPEREAL)m_AveCount; + + m_pFFTAveBuf[j] = MLOG10(m_pFFTPwrAveBuf[j] + m_K_C) + m_K_B; + m_pFFTAveBuf[k] = MLOG10(m_pFFTPwrAveBuf[k] + m_K_C) + m_K_B; + + } + + a[0] *= a[0]; //calc DC term + xr = a[m]*a[m]+a[m+1]*a[m+1]; //calculate N/4(middle) term + + //xr is real power a[0] is imag power terms + //perform moving average on power up to m_AveSize then do exponential averaging after that + if(m_TotalCount <= m_AveSize) + { + m_pFFTSumBuf[0] = m_pFFTSumBuf[0] + a[0]; + m_pFFTSumBuf[n/2] = m_pFFTSumBuf[n/2] + xr; + } + else + { + m_pFFTSumBuf[0] = m_pFFTSumBuf[0] - m_pFFTPwrAveBuf[0] + a[0]; + m_pFFTSumBuf[n/2] = m_pFFTSumBuf[n/2] - m_pFFTPwrAveBuf[n/2] + xr; + } + m_pFFTPwrAveBuf[0] = m_pFFTSumBuf[0]/(TYPEREAL)m_AveCount; + m_pFFTPwrAveBuf[n/2] = m_pFFTSumBuf[n/2]/(TYPEREAL)m_AveCount; + + m_pFFTAveBuf[0] = MLOG10(m_pFFTPwrAveBuf[0] + m_K_C) + m_K_B; + m_pFFTAveBuf[n/2] = MLOG10(m_pFFTPwrAveBuf[n/2] + m_K_C) + m_K_B; + +} + + +/////////////////////////////////////////////////////////////////// +// Routine calculates complex FFT +/////////////////////////////////////////////////////////////////// +void CFft::CpxFFT(qint32 n, TYPEREAL *a, TYPEREAL *w) +{ +qint32 j, j1, j2, j3, l; +TYPEREAL x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; + + m_TotalCount++; + if(m_AveCount < m_AveSize) + m_AveCount++; + l = 2; + if (n > 8) { + cft1st(n, a, w); + l = 8; + while ((l << 2) < n) { + cftmdl(n, l, a, w); + l <<= 2; + } + } + if ((l << 2) == n) { + for (j = 0; j < l; j += 2) { + j1 = j + l; + j2 = j1 + l; + j3 = j2 + l; + x0r = a[j] + a[j1]; + x0i = a[j + 1] + a[j1 + 1]; + x1r = a[j] - a[j1]; + x1i = a[j + 1] - a[j1 + 1]; + x2r = a[j2] + a[j3]; + x2i = a[j2 + 1] + a[j3 + 1]; + x3r = a[j2] - a[j3]; + x3i = a[j2 + 1] - a[j3 + 1]; + a[j] = x0r + x2r; + a[j + 1] = x0i + x2i; + a[j2] = x0r - x2r; + a[j2 + 1] = x0i - x2i; + a[j1] = x1r - x3i; + a[j1 + 1] = x1i + x3r; + a[j3] = x1r + x3i; + a[j3 + 1] = x1i - x3r; + } + } else { + for (j = 0; j < l; j += 2) { + j1 = j + l; + x0r = a[j] - a[j1]; + x0i = a[j + 1] - a[j1 + 1]; + a[j] += a[j1]; + a[j + 1] += a[j1 + 1]; + a[j1] = x0r; + a[j1 + 1] = x0i; + } + } + //n = 2*FFTSIZE + n = n>>1; + //now n = FFTSIZE + + // FFT output index 0 to N/2-1 + // is frequency output 0 to +Fs/2 Hz ( 0 Hz DC term ) + for( l=0,j=n/2; j 2) { + nwh = nw >> 1; + delta = MATAN(1.0) / nwh; + w[0] = 1; + w[1] = 0; + w[nwh] = MCOS(delta * nwh); + w[nwh + 1] = w[nwh]; + if (nwh > 2) { + for (j = 2; j < nwh; j += 2) { + x = MCOS(delta * j); + y = MSIN(delta * j); + w[j] = x; + w[j + 1] = y; + w[nw - j] = y; + w[nw - j + 1] = x; + } + bitrv2(nw, ip + 2, w); + } + } +} + +/////////////////////////////////////////////////////////////////// +void CFft::makect(qint32 nc, qint32 *ip, TYPEREAL *c) +{ +qint32 j, nch; +TYPEREAL delta; + + ip[1] = nc; + if (nc > 1) { + nch = nc >> 1; + delta = MATAN(1.0) / nch; + c[0] = MCOS(delta * nch); + c[nch] = 0.5 * c[0]; + for (j = 1; j < nch; j++) { + c[j] = 0.5 * MCOS(delta * j); + c[nc - j] = 0.5 * MSIN(delta * j); + } + } +} + +/////////////////////////////////////////////////////////////////// +/* -------- child routines -------- */ +/////////////////////////////////////////////////////////////////// +void CFft::bitrv2(qint32 n, qint32 *ip, TYPEREAL *a) +{ +qint32 j, j1, k, k1, l, m, m2; +TYPEREAL xr, xi, yr, yi; + + ip[0] = 0; + l = n; + m = 1; + while ((m << 3) < l) { + l >>= 1; + for (j = 0; j < m; j++) { + ip[m + j] = ip[j] + l; + } + m <<= 1; + } + m2 = 2 * m; + if ((m << 3) == l) { + for (k = 0; k < m; k++) { + for (j = 0; j < k; j++) { + j1 = 2 * j + ip[k]; + k1 = 2 * k + ip[j]; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m2; + k1 += 2 * m2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m2; + k1 -= m2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m2; + k1 += 2 * m2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + } + j1 = 2 * k + m2 + ip[k]; + k1 = j1 + m2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + } + } else { + for (k = 1; k < m; k++) { + for (j = 0; j < k; j++) { + j1 = 2 * j + ip[k]; + k1 = 2 * k + ip[j]; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m2; + k1 += m2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + } + } + } +} + +/////////////////////////////////////////////////////////////////// +void CFft::cftfsub(qint32 n, TYPEREAL *a, TYPEREAL *w) +{ +qint32 j, j1, j2, j3, l; +TYPEREAL x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; + + l = 2; + if (n > 8) { + cft1st(n, a, w); + l = 8; + while ((l << 2) < n) { + cftmdl(n, l, a, w); + l <<= 2; + } + } + if ((l << 2) == n) { + for (j = 0; j < l; j += 2) { + j1 = j + l; + j2 = j1 + l; + j3 = j2 + l; + x0r = a[j] + a[j1]; + x0i = a[j + 1] + a[j1 + 1]; + x1r = a[j] - a[j1]; + x1i = a[j + 1] - a[j1 + 1]; + x2r = a[j2] + a[j3]; + x2i = a[j2 + 1] + a[j3 + 1]; + x3r = a[j2] - a[j3]; + x3i = a[j2 + 1] - a[j3 + 1]; + a[j] = x0r + x2r; + a[j + 1] = x0i + x2i; + a[j2] = x0r - x2r; + a[j2 + 1] = x0i - x2i; + a[j1] = x1r - x3i; + a[j1 + 1] = x1i + x3r; + a[j3] = x1r + x3i; + a[j3 + 1] = x1i - x3r; + } + } else { + for (j = 0; j < l; j += 2) { + j1 = j + l; + x0r = a[j] - a[j1]; + x0i = a[j + 1] - a[j1 + 1]; + a[j] += a[j1]; + a[j + 1] += a[j1 + 1]; + a[j1] = x0r; + a[j1 + 1] = x0i; + } + } +} + +/////////////////////////////////////////////////////////////////// +void CFft::cft1st(qint32 n, TYPEREAL *a, TYPEREAL *w) +{ +qint32 j, k1, k2; +TYPEREAL wk1r, wk1i, wk2r, wk2i, wk3r, wk3i; +TYPEREAL x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; + + x0r = a[0] + a[2]; + x0i = a[1] + a[3]; + x1r = a[0] - a[2]; + x1i = a[1] - a[3]; + x2r = a[4] + a[6]; + x2i = a[5] + a[7]; + x3r = a[4] - a[6]; + x3i = a[5] - a[7]; + a[0] = x0r + x2r; + a[1] = x0i + x2i; + a[4] = x0r - x2r; + a[5] = x0i - x2i; + a[2] = x1r - x3i; + a[3] = x1i + x3r; + a[6] = x1r + x3i; + a[7] = x1i - x3r; + wk1r = w[2]; + x0r = a[8] + a[10]; + x0i = a[9] + a[11]; + x1r = a[8] - a[10]; + x1i = a[9] - a[11]; + x2r = a[12] + a[14]; + x2i = a[13] + a[15]; + x3r = a[12] - a[14]; + x3i = a[13] - a[15]; + a[8] = x0r + x2r; + a[9] = x0i + x2i; + a[12] = x2i - x0i; + a[13] = x0r - x2r; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[10] = wk1r * (x0r - x0i); + a[11] = wk1r * (x0r + x0i); + x0r = x3i + x1r; + x0i = x3r - x1i; + a[14] = wk1r * (x0i - x0r); + a[15] = wk1r * (x0i + x0r); + k1 = 0; + for (j = 16; j < n; j += 16) { + k1 += 2; + k2 = 2 * k1; + wk2r = w[k1]; + wk2i = w[k1 + 1]; + wk1r = w[k2]; + wk1i = w[k2 + 1]; + wk3r = wk1r - 2 * wk2i * wk1i; + wk3i = 2 * wk2i * wk1r - wk1i; + x0r = a[j] + a[j + 2]; + x0i = a[j + 1] + a[j + 3]; + x1r = a[j] - a[j + 2]; + x1i = a[j + 1] - a[j + 3]; + x2r = a[j + 4] + a[j + 6]; + x2i = a[j + 5] + a[j + 7]; + x3r = a[j + 4] - a[j + 6]; + x3i = a[j + 5] - a[j + 7]; + a[j] = x0r + x2r; + a[j + 1] = x0i + x2i; + x0r -= x2r; + x0i -= x2i; + a[j + 4] = wk2r * x0r - wk2i * x0i; + a[j + 5] = wk2r * x0i + wk2i * x0r; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j + 2] = wk1r * x0r - wk1i * x0i; + a[j + 3] = wk1r * x0i + wk1i * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + a[j + 6] = wk3r * x0r - wk3i * x0i; + a[j + 7] = wk3r * x0i + wk3i * x0r; + wk1r = w[k2 + 2]; + wk1i = w[k2 + 3]; + wk3r = wk1r - 2 * wk2r * wk1i; + wk3i = 2 * wk2r * wk1r - wk1i; + x0r = a[j + 8] + a[j + 10]; + x0i = a[j + 9] + a[j + 11]; + x1r = a[j + 8] - a[j + 10]; + x1i = a[j + 9] - a[j + 11]; + x2r = a[j + 12] + a[j + 14]; + x2i = a[j + 13] + a[j + 15]; + x3r = a[j + 12] - a[j + 14]; + x3i = a[j + 13] - a[j + 15]; + a[j + 8] = x0r + x2r; + a[j + 9] = x0i + x2i; + x0r -= x2r; + x0i -= x2i; + a[j + 12] = -wk2i * x0r - wk2r * x0i; + a[j + 13] = -wk2i * x0i + wk2r * x0r; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j + 10] = wk1r * x0r - wk1i * x0i; + a[j + 11] = wk1r * x0i + wk1i * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + a[j + 14] = wk3r * x0r - wk3i * x0i; + a[j + 15] = wk3r * x0i + wk3i * x0r; + } +} + +/////////////////////////////////////////////////////////////////// +void CFft::cftmdl(qint32 n, qint32 l, TYPEREAL *a, TYPEREAL *w) +{ +qint32 j, j1, j2, j3, k, k1, k2, m, m2; +TYPEREAL wk1r, wk1i, wk2r, wk2i, wk3r, wk3i; +TYPEREAL x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; + + m = l << 2; + for (j = 0; j < l; j += 2) { + j1 = j + l; + j2 = j1 + l; + j3 = j2 + l; + x0r = a[j] + a[j1]; + x0i = a[j + 1] + a[j1 + 1]; + x1r = a[j] - a[j1]; + x1i = a[j + 1] - a[j1 + 1]; + x2r = a[j2] + a[j3]; + x2i = a[j2 + 1] + a[j3 + 1]; + x3r = a[j2] - a[j3]; + x3i = a[j2 + 1] - a[j3 + 1]; + a[j] = x0r + x2r; + a[j + 1] = x0i + x2i; + a[j2] = x0r - x2r; + a[j2 + 1] = x0i - x2i; + a[j1] = x1r - x3i; + a[j1 + 1] = x1i + x3r; + a[j3] = x1r + x3i; + a[j3 + 1] = x1i - x3r; + } + wk1r = w[2]; + for (j = m; j < l + m; j += 2) { + j1 = j + l; + j2 = j1 + l; + j3 = j2 + l; + x0r = a[j] + a[j1]; + x0i = a[j + 1] + a[j1 + 1]; + x1r = a[j] - a[j1]; + x1i = a[j + 1] - a[j1 + 1]; + x2r = a[j2] + a[j3]; + x2i = a[j2 + 1] + a[j3 + 1]; + x3r = a[j2] - a[j3]; + x3i = a[j2 + 1] - a[j3 + 1]; + a[j] = x0r + x2r; + a[j + 1] = x0i + x2i; + a[j2] = x2i - x0i; + a[j2 + 1] = x0r - x2r; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j1] = wk1r * (x0r - x0i); + a[j1 + 1] = wk1r * (x0r + x0i); + x0r = x3i + x1r; + x0i = x3r - x1i; + a[j3] = wk1r * (x0i - x0r); + a[j3 + 1] = wk1r * (x0i + x0r); + } + k1 = 0; + m2 = 2 * m; + for (k = m2; k < n; k += m2) { + k1 += 2; + k2 = 2 * k1; + wk2r = w[k1]; + wk2i = w[k1 + 1]; + wk1r = w[k2]; + wk1i = w[k2 + 1]; + wk3r = wk1r - 2 * wk2i * wk1i; + wk3i = 2 * wk2i * wk1r - wk1i; + for (j = k; j < l + k; j += 2) { + j1 = j + l; + j2 = j1 + l; + j3 = j2 + l; + x0r = a[j] + a[j1]; + x0i = a[j + 1] + a[j1 + 1]; + x1r = a[j] - a[j1]; + x1i = a[j + 1] - a[j1 + 1]; + x2r = a[j2] + a[j3]; + x2i = a[j2 + 1] + a[j3 + 1]; + x3r = a[j2] - a[j3]; + x3i = a[j2 + 1] - a[j3 + 1]; + a[j] = x0r + x2r; + a[j + 1] = x0i + x2i; + x0r -= x2r; + x0i -= x2i; + a[j2] = wk2r * x0r - wk2i * x0i; + a[j2 + 1] = wk2r * x0i + wk2i * x0r; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j1] = wk1r * x0r - wk1i * x0i; + a[j1 + 1] = wk1r * x0i + wk1i * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + a[j3] = wk3r * x0r - wk3i * x0i; + a[j3 + 1] = wk3r * x0i + wk3i * x0r; + } + wk1r = w[k2 + 2]; + wk1i = w[k2 + 3]; + wk3r = wk1r - 2 * wk2r * wk1i; + wk3i = 2 * wk2r * wk1r - wk1i; + for (j = k + m; j < l + (k + m); j += 2) { + j1 = j + l; + j2 = j1 + l; + j3 = j2 + l; + x0r = a[j] + a[j1]; + x0i = a[j + 1] + a[j1 + 1]; + x1r = a[j] - a[j1]; + x1i = a[j + 1] - a[j1 + 1]; + x2r = a[j2] + a[j3]; + x2i = a[j2 + 1] + a[j3 + 1]; + x3r = a[j2] - a[j3]; + x3i = a[j2 + 1] - a[j3 + 1]; + a[j] = x0r + x2r; + a[j + 1] = x0i + x2i; + x0r -= x2r; + x0i -= x2i; + a[j2] = -wk2i * x0r - wk2r * x0i; + a[j2 + 1] = -wk2i * x0i + wk2r * x0r; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j1] = wk1r * x0r - wk1i * x0i; + a[j1 + 1] = wk1r * x0i + wk1i * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + a[j3] = wk3r * x0r - wk3i * x0i; + a[j3 + 1] = wk3r * x0i + wk3i * x0r; + } + } +} + +void CFft::bitrv2conj(int n, int *ip, TYPEREAL *a) +{ + int j, j1, k, k1, l, m, m2; + TYPEREAL xr, xi, yr, yi; + + ip[0] = 0; + l = n; + m = 1; + while ((m << 3) < l) { + l >>= 1; + for (j = 0; j < m; j++) { + ip[m + j] = ip[j] + l; + } + m <<= 1; + } + m2 = 2 * m; + if ((m << 3) == l) { + for (k = 0; k < m; k++) { + for (j = 0; j < k; j++) { + j1 = 2 * j + ip[k]; + k1 = 2 * k + ip[j]; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m2; + k1 += 2 * m2; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m2; + k1 -= m2; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m2; + k1 += 2 * m2; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + } + k1 = 2 * k + ip[k]; + a[k1 + 1] = -a[k1 + 1]; + j1 = k1 + m2; + k1 = j1 + m2; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + k1 += m2; + a[k1 + 1] = -a[k1 + 1]; + } + } else { + a[1] = -a[1]; + a[m2 + 1] = -a[m2 + 1]; + for (k = 1; k < m; k++) { + for (j = 0; j < k; j++) { + j1 = 2 * j + ip[k]; + k1 = 2 * k + ip[j]; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m2; + k1 += m2; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + } + k1 = 2 * k + ip[k]; + a[k1 + 1] = -a[k1 + 1]; + a[k1 + m2 + 1] = -a[k1 + m2 + 1]; + } + } +} + +void CFft::cftbsub(int n, TYPEREAL *a, TYPEREAL *w) +{ + int j, j1, j2, j3, l; + TYPEREAL x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; + + l = 2; + if (n > 8) { + cft1st(n, a, w); + l = 8; + while ((l << 2) < n) { + cftmdl(n, l, a, w); + l <<= 2; + } + } + if ((l << 2) == n) { + for (j = 0; j < l; j += 2) { + j1 = j + l; + j2 = j1 + l; + j3 = j2 + l; + x0r = a[j] + a[j1]; + x0i = -a[j + 1] - a[j1 + 1]; + x1r = a[j] - a[j1]; + x1i = -a[j + 1] + a[j1 + 1]; + x2r = a[j2] + a[j3]; + x2i = a[j2 + 1] + a[j3 + 1]; + x3r = a[j2] - a[j3]; + x3i = a[j2 + 1] - a[j3 + 1]; + a[j] = x0r + x2r; + a[j + 1] = x0i - x2i; + a[j2] = x0r - x2r; + a[j2 + 1] = x0i + x2i; + a[j1] = x1r - x3i; + a[j1 + 1] = x1i - x3r; + a[j3] = x1r + x3i; + a[j3 + 1] = x1i + x3r; + } + } else { + for (j = 0; j < l; j += 2) { + j1 = j + l; + x0r = a[j] - a[j1]; + x0i = -a[j + 1] + a[j1 + 1]; + a[j] += a[j1]; + a[j + 1] = -a[j + 1] - a[j1 + 1]; + a[j1] = x0r; + a[j1 + 1] = x0i; + } + } +} + + diff --git a/src/fmdsp/fft.h b/src/fmdsp/fft.h index c2b1bf4..140458b 100644 --- a/src/fmdsp/fft.h +++ b/src/fmdsp/fft.h @@ -1,90 +1,90 @@ -// fft.h: interface for the CFft class. -// -// This is a somewhat modified version of Takuya OOURA's -// original radix 4 FFT package. -// A C++ wrapper around his code plus some specialized methods -// for displaying power vs frequency -// -//Copyright(C) 1996-1998 Takuya OOURA -// (email: ooura@mmm.t.u-tokyo.ac.jp). -// -// History: -// 2010-09-15 Initial creation MSW -// 2011-03-27 Initial release -////////////////////////////////////////////////////////////////////// -#ifndef FFT_H -#define FFT_H - -#include "datatypes.h" - -#include - -#define MAX_FFT_SIZE 65536 -#define MIN_FFT_SIZE 512 - -class CFft -{ -public: - CFft(); - virtual ~CFft(); - void SetFFTParams( qint32 size, - bool invert, - TYPEREAL dBCompensation, - TYPEREAL SampleFreq); - //Methods to obtain spectrum formated power vs frequency - void SetFFTAve( qint32 ave); - void ResetFFT(); - bool GetScreenIntegerFFTData(qint32 MaxHeight, qint32 MaxWidth, - TYPEREAL MaxdB, TYPEREAL MindB, - qint32 StartFreq, qint32 StopFreq, - qint32* OutBuf ); - qint32 PutInDisplayFFT(qint32 n, TYPECPX* InBuf); - - //Methods for doing Fast convolutions using forward and reverse FFT - void FwdFFT( TYPECPX* pInOutBuf); - void RevFFT( TYPECPX* pInOutBuf); - -private: - void FreeMemory(); - void makewt(qint32 nw, qint32 *ip, TYPEREAL *w); - void makect(qint32 nc, qint32 *ip, TYPEREAL *c); - void bitrv2(qint32 n, qint32 *ip, TYPEREAL *a); - void cftfsub(qint32 n, TYPEREAL *a, TYPEREAL *w); - void rftfsub(qint32 n, TYPEREAL *a, qint32 nc, TYPEREAL *c); - void CpxFFT(qint32 n, TYPEREAL *a, TYPEREAL *w); - void cft1st(qint32 n, TYPEREAL *a, TYPEREAL *w); - void cftmdl(qint32 n, qint32 l, TYPEREAL *a, TYPEREAL *w); - void bitrv2conj(int n, int *ip, TYPEREAL *a); - void cftbsub(int n, TYPEREAL *a, TYPEREAL *w); - - bool m_Overload; - bool m_Invert; - qint32 m_AveCount; - qint32 m_TotalCount; - qint32 m_FFTSize; - qint32 m_LastFFTSize; - qint32 m_AveSize; - qint32 m_StartFreq; - qint32 m_StopFreq; - qint32 m_BinMin; - qint32 m_BinMax; - qint32 m_PlotWidth; - - TYPEREAL m_K_C; - TYPEREAL m_K_B; - TYPEREAL m_dBCompensation; - TYPEREAL m_SampleFreq; - qint32* m_pWorkArea; - qint32* m_pTranslateTbl; - TYPEREAL* m_pSinCosTbl; - TYPEREAL* m_pWindowTbl; - TYPEREAL* m_pFFTPwrAveBuf; - TYPEREAL* m_pFFTAveBuf; - TYPEREAL* m_pFFTSumBuf; - TYPEREAL* m_pFFTInBuf; -#ifdef FMDSP_THREAD_SAFE - mutable std::mutex m_Mutex; //for keeping threads from stomping on each other -#endif -}; - -#endif // FFT_H +// fft.h: interface for the CFft class. +// +// This is a somewhat modified version of Takuya OOURA's +// original radix 4 FFT package. +// A C++ wrapper around his code plus some specialized methods +// for displaying power vs frequency +// +//Copyright(C) 1996-1998 Takuya OOURA +// (email: ooura@mmm.t.u-tokyo.ac.jp). +// +// History: +// 2010-09-15 Initial creation MSW +// 2011-03-27 Initial release +////////////////////////////////////////////////////////////////////// +#ifndef FFT_H +#define FFT_H + +#include "datatypes.h" + +#include + +#define MAX_FFT_SIZE 65536 +#define MIN_FFT_SIZE 512 + +class CFft +{ +public: + CFft(); + virtual ~CFft(); + void SetFFTParams( qint32 size, + bool invert, + TYPEREAL dBCompensation, + TYPEREAL SampleFreq); + //Methods to obtain spectrum formated power vs frequency + void SetFFTAve( qint32 ave); + void ResetFFT(); + bool GetScreenIntegerFFTData(qint32 MaxHeight, qint32 MaxWidth, + TYPEREAL MaxdB, TYPEREAL MindB, + qint32 StartFreq, qint32 StopFreq, + qint32* OutBuf ); + qint32 PutInDisplayFFT(qint32 n, TYPECPX* InBuf); + + //Methods for doing Fast convolutions using forward and reverse FFT + void FwdFFT( TYPECPX* pInOutBuf); + void RevFFT( TYPECPX* pInOutBuf); + +private: + void FreeMemory(); + void makewt(qint32 nw, qint32 *ip, TYPEREAL *w); + void makect(qint32 nc, qint32 *ip, TYPEREAL *c); + void bitrv2(qint32 n, qint32 *ip, TYPEREAL *a); + void cftfsub(qint32 n, TYPEREAL *a, TYPEREAL *w); + void rftfsub(qint32 n, TYPEREAL *a, qint32 nc, TYPEREAL *c); + void CpxFFT(qint32 n, TYPEREAL *a, TYPEREAL *w); + void cft1st(qint32 n, TYPEREAL *a, TYPEREAL *w); + void cftmdl(qint32 n, qint32 l, TYPEREAL *a, TYPEREAL *w); + void bitrv2conj(int n, int *ip, TYPEREAL *a); + void cftbsub(int n, TYPEREAL *a, TYPEREAL *w); + + bool m_Overload; + bool m_Invert; + qint32 m_AveCount; + qint32 m_TotalCount; + qint32 m_FFTSize; + qint32 m_LastFFTSize; + qint32 m_AveSize; + qint32 m_StartFreq; + qint32 m_StopFreq; + qint32 m_BinMin; + qint32 m_BinMax; + qint32 m_PlotWidth; + + TYPEREAL m_K_C; + TYPEREAL m_K_B; + TYPEREAL m_dBCompensation; + TYPEREAL m_SampleFreq; + qint32* m_pWorkArea; + qint32* m_pTranslateTbl; + TYPEREAL* m_pSinCosTbl; + TYPEREAL* m_pWindowTbl; + TYPEREAL* m_pFFTPwrAveBuf; + TYPEREAL* m_pFFTAveBuf; + TYPEREAL* m_pFFTSumBuf; + TYPEREAL* m_pFFTInBuf; +#ifdef FMDSP_THREAD_SAFE + mutable std::mutex m_Mutex; //for keeping threads from stomping on each other +#endif +}; + +#endif // FFT_H diff --git a/src/fmdsp/filtercoef.h b/src/fmdsp/filtercoef.h index 793c9c1..c74f49f 100644 --- a/src/fmdsp/filtercoef.h +++ b/src/fmdsp/filtercoef.h @@ -1,453 +1,453 @@ -// filtercoef.h: filte coefficients for various halfband filters -// -// History: -// 2010-09-15 Initial creation MSW -// 2011-03-27 Initial release -////////////////////////////////////////////////////////////////////// -//========================================================================================== -// + + + This Software is released under the "Simplified BSD License" + + + -//Copyright 2010 Moe Wheatley. All rights reserved. -// -//Redistribution and use in source and binary forms, with or without modification, are -//permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other materials -// provided with the distribution. -// -//THIS SOFTWARE IS PROVIDED BY Moe Wheatley ``AS IS'' AND ANY EXPRESS OR IMPLIED -//WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -//FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Moe Wheatley OR -//CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -//CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -//SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -//ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -//NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -//ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -//The views and conclusions contained in the software and documentation are those of the -//authors and should not be interpreted as representing official policies, either expressed -//or implied, of Moe Wheatley. -//============================================================================= -#ifndef FILTERCOEF_H -#define FILTERCOEF_H - -////////////////////////////////////////////////////////////////////// -// Filter -140dB Alias free normalized bandwidth constants for each type of filter -// These values are used to determine what filter to use in the decimation -// chains as a function of desired maximum output bandwidth -////////////////////////////////////////////////////////////////////// - -//normalized alias free bandwidths for the filters -#define CIC3_MAX (.5-.4985) -#define HB11TAP_MAX (.5-.475) -#define HB15TAP_MAX (.5-.451) -#define HB19TAP_MAX (.5-.428) -#define HB23TAP_MAX (.5-.409) -#define HB27TAP_MAX (.5-.392) -#define HB31TAP_MAX (.5-.378) -#define HB35TAP_MAX (.5-.366) -#define HB39TAP_MAX (.5-.356) -#define HB43TAP_MAX (.5-.347) -#define HB47TAP_MAX (.5-.340) -#define HB51TAP_MAX (.5-.333) - -//////////////////////////////////////////////////////////////////// -// The following coefficients were designed with -// MatLab for best alias rejection at -140dB -//////////////////////////////////////////////////////////////////// -#define HB11TAP_LENGTH 11 -const TYPEREAL HB11TAP_H[HB11TAP_LENGTH] = -{ - 0.0060431029837374152, - 0.0, - -0.049372515458761493, - 0.0, - 0.29332944952052842, - 0.5, - 0.29332944952052842, - 0.0, - -0.049372515458761493, - 0.0, - 0.0060431029837374152, -}; - -#define HB15TAP_LENGTH 15 -const TYPEREAL HB15TAP_H[HB15TAP_LENGTH] = -{ - -0.001442203300285281, - 0.0, - 0.013017512802724852, - 0.0, - -0.061653278604903369, - 0.0, - 0.30007792316024057, - 0.5, - 0.30007792316024057, - 0.0, - -0.061653278604903369, - 0.0, - 0.013017512802724852, - 0.0, - -0.001442203300285281 -}; - -#define HB19TAP_LENGTH 19 -const TYPEREAL HB19TAP_H[HB19TAP_LENGTH] = -{ - 0.00042366527106480427, - 0.0, - -0.0040717333369021894, - 0.0, - 0.019895653881950692, - 0.0, - -0.070740034412329067, - 0.0, - 0.30449249772844139, - 0.5, - 0.30449249772844139, - 0.0, - -0.070740034412329067, - 0.0, - 0.019895653881950692, - 0.0, - -0.0040717333369021894, - 0.0, - 0.00042366527106480427 -}; - -#define HB23TAP_LENGTH 23 -const TYPEREAL HB23TAP_H[HB23TAP_LENGTH] = -{ - -0.00014987651418332164, - 0.0, - 0.0014748633283609852, - 0.0, - -0.0074416944990005314, - 0.0, - 0.026163522731980929, - 0.0, - -0.077593699116544707, - 0.0, - 0.30754683719791986, - 0.5, - 0.30754683719791986, - 0.0, - -0.077593699116544707, - 0.0, - 0.026163522731980929, - 0.0, - -0.0074416944990005314, - 0.0, - 0.0014748633283609852, - 0.0, - -0.00014987651418332164 -}; -#define HB27TAP_LENGTH 27 -const TYPEREAL HB27TAP_H[HB27TAP_LENGTH] = -{ - 0.000063730426952664685, - 0.0, - -0.00061985193978569082, - 0.0, - 0.0031512504783365756, - 0.0, - -0.011173151342856621, - 0.0, - 0.03171888754393197, - 0.0, - -0.082917863582770729, - 0.0, - 0.3097770473566307, - 0.5, - 0.3097770473566307, - 0.0, - -0.082917863582770729, - 0.0, - 0.03171888754393197, - 0.0, - -0.011173151342856621, - 0.0, - 0.0031512504783365756, - 0.0, - -0.00061985193978569082, - 0.0, - 0.000063730426952664685 -}; - -#define HB31TAP_LENGTH 31 -const TYPEREAL HB31TAP_H[HB31TAP_LENGTH] = -{ - -0.000030957335326552226, - 0.0, - 0.00029271992847303054, - 0.0, - -0.0014770381124258423, - 0.0, - 0.0052539088990950535, - 0.0, - -0.014856378748476874, - 0.0, - 0.036406651919555999, - 0.0, - -0.08699862567952929, - 0.0, - 0.31140967076042625, - 0.5, - 0.31140967076042625, - 0.0, - -0.08699862567952929, - 0.0, - 0.036406651919555999, - 0.0, - -0.014856378748476874, - 0.0, - 0.0052539088990950535, - 0.0, - -0.0014770381124258423, - 0.0, - 0.00029271992847303054, - 0.0, - -0.000030957335326552226 -}; -#define HB35TAP_LENGTH 35 -const TYPEREAL HB35TAP_H[HB35TAP_LENGTH] = -{ - 0.000017017718072971716, - 0.0, - -0.00015425042851962818, - 0.0, - 0.00076219685751140838, - 0.0, - -0.002691614694785393, - 0.0, - 0.0075927497927344764, - 0.0, - -0.018325727896057686, - 0.0, - 0.040351004914363969, - 0.0, - -0.090198224668969554, - 0.0, - 0.31264689763504327, - 0.5, - 0.31264689763504327, - 0.0, - -0.090198224668969554, - 0.0, - 0.040351004914363969, - 0.0, - -0.018325727896057686, - 0.0, - 0.0075927497927344764, - 0.0, - -0.002691614694785393, - 0.0, - 0.00076219685751140838, - 0.0, - -0.00015425042851962818, - 0.0, - 0.000017017718072971716 -}; -#define HB39TAP_LENGTH 39 -const TYPEREAL HB39TAP_H[HB39TAP_LENGTH] = -{ - -0.000010175082832074367, - 0.0, - 0.000088036416015024345, - 0.0, - -0.00042370835558387595, - 0.0, - 0.0014772557414459019, - 0.0, - -0.0041468438954260153, - 0.0, - 0.0099579126901608011, - 0.0, - -0.021433527104289002, - 0.0, - 0.043598963493432855, - 0.0, - -0.092695953625928404, - 0.0, - 0.31358799113382152, - 0.5, - 0.31358799113382152, - 0, - -0.092695953625928404, - 0.0, - 0.043598963493432855, - 0.0, - -0.021433527104289002, - 0.0, - 0.0099579126901608011, - 0.0, - -0.0041468438954260153, - 0.0, - 0.0014772557414459019, - 0.0, - -0.00042370835558387595, - 0.0, - 0.000088036416015024345, - 0.0, - -0.000010175082832074367 -}; -#define HB43TAP_LENGTH 43 -const TYPEREAL HB43TAP_H[HB43TAP_LENGTH] = -{ - 0.0000067666739082756387, - 0.0, - -0.000055275221547958285, - 0.0, - 0.00025654074579418561, - 0.0, - -0.0008748125689163153, - 0.0, - 0.0024249876017061502, - 0.0, - -0.0057775190656021748, - 0.0, - 0.012299834239523121, - 0.0, - -0.024244050662087069, - 0.0, - 0.046354303503099069, - 0.0, - -0.094729903598633314, - 0.0, - 0.31433918020123208, - 0.5, - 0.31433918020123208, - 0.0, - -0.094729903598633314, - 0.0, - 0.046354303503099069, - 0.0, - -0.024244050662087069, - 0.0, - 0.012299834239523121, - 0.0, - -0.0057775190656021748, - 0.0, - 0.0024249876017061502, - 0.0, - -0.0008748125689163153, - 0.0, - 0.00025654074579418561, - 0.0, - -0.000055275221547958285, - 0.0, - 0.0000067666739082756387 -}; -#define HB47TAP_LENGTH 47 -const TYPEREAL HB47TAP_H[HB47TAP_LENGTH] = -{ - -0.0000045298314172004251, - 0.0, - 0.000035333704512843228, - 0.0, - -0.00015934776420643447, - 0.0, - 0.0005340788063118928, - 0.0, - -0.0014667949695500761, - 0.0, - 0.0034792089350833247, - 0.0, - -0.0073794356720317733, - 0.0, - 0.014393786384683398, - 0.0, - -0.026586603160193314, - 0.0, - 0.048538673667907428, - 0.0, - -0.09629115286535718, - 0.0, - 0.31490673428547367, - 0.5, - 0.31490673428547367, - 0.0, - -0.09629115286535718, - 0.0, - 0.048538673667907428, - 0.0, - -0.026586603160193314, - 0.0, - 0.014393786384683398, - 0.0, - -0.0073794356720317733, - 0.0, - 0.0034792089350833247, - 0.0, - -0.0014667949695500761, - 0.0, - 0.0005340788063118928, - 0.0, - -0.00015934776420643447, - 0.0, - 0.000035333704512843228, - 0.0, - -0.0000045298314172004251 -}; -#define HB51TAP_LENGTH 51 -const TYPEREAL HB51TAP_H[HB51TAP_LENGTH] = -{ - 0.0000033359253688981639, - 0.0, - -0.000024584155158361803, - 0.0, - 0.00010677777483317733, - 0.0, - -0.00034890723143173914, - 0.0, - 0.00094239127078189603, - 0.0, - -0.0022118302078923137, - 0.0, - 0.0046575030752162277, - 0.0, - -0.0090130973415220566, - 0.0, - 0.016383673864361164, - 0.0, - -0.028697281101743237, - 0.0, - 0.05043292242400841, - 0.0, - -0.097611898315791965, - 0.0, - 0.31538104435015801, - 0.5, - 0.31538104435015801, - 0.0, - -0.097611898315791965, - 0.0, - 0.05043292242400841, - 0.0, - -0.028697281101743237, - 0.0, - 0.016383673864361164, - 0.0, - -0.0090130973415220566, - 0.0, - 0.0046575030752162277, - 0.0, - -0.0022118302078923137, - 0.0, - 0.00094239127078189603, - 0.0, - -0.00034890723143173914, - 0.0, - 0.00010677777483317733, - 0.0, - -0.000024584155158361803, - 0.0, - 0.0000033359253688981639 -}; - -#endif // FILTERCOEF_H - +// filtercoef.h: filte coefficients for various halfband filters +// +// History: +// 2010-09-15 Initial creation MSW +// 2011-03-27 Initial release +////////////////////////////////////////////////////////////////////// +//========================================================================================== +// + + + This Software is released under the "Simplified BSD License" + + + +//Copyright 2010 Moe Wheatley. All rights reserved. +// +//Redistribution and use in source and binary forms, with or without modification, are +//permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other materials +// provided with the distribution. +// +//THIS SOFTWARE IS PROVIDED BY Moe Wheatley ``AS IS'' AND ANY EXPRESS OR IMPLIED +//WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +//FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Moe Wheatley OR +//CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +//CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +//SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +//ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +//NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +//ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +//The views and conclusions contained in the software and documentation are those of the +//authors and should not be interpreted as representing official policies, either expressed +//or implied, of Moe Wheatley. +//============================================================================= +#ifndef FILTERCOEF_H +#define FILTERCOEF_H + +////////////////////////////////////////////////////////////////////// +// Filter -140dB Alias free normalized bandwidth constants for each type of filter +// These values are used to determine what filter to use in the decimation +// chains as a function of desired maximum output bandwidth +////////////////////////////////////////////////////////////////////// + +//normalized alias free bandwidths for the filters +#define CIC3_MAX (.5-.4985) +#define HB11TAP_MAX (.5-.475) +#define HB15TAP_MAX (.5-.451) +#define HB19TAP_MAX (.5-.428) +#define HB23TAP_MAX (.5-.409) +#define HB27TAP_MAX (.5-.392) +#define HB31TAP_MAX (.5-.378) +#define HB35TAP_MAX (.5-.366) +#define HB39TAP_MAX (.5-.356) +#define HB43TAP_MAX (.5-.347) +#define HB47TAP_MAX (.5-.340) +#define HB51TAP_MAX (.5-.333) + +//////////////////////////////////////////////////////////////////// +// The following coefficients were designed with +// MatLab for best alias rejection at -140dB +//////////////////////////////////////////////////////////////////// +#define HB11TAP_LENGTH 11 +const TYPEREAL HB11TAP_H[HB11TAP_LENGTH] = +{ + 0.0060431029837374152, + 0.0, + -0.049372515458761493, + 0.0, + 0.29332944952052842, + 0.5, + 0.29332944952052842, + 0.0, + -0.049372515458761493, + 0.0, + 0.0060431029837374152, +}; + +#define HB15TAP_LENGTH 15 +const TYPEREAL HB15TAP_H[HB15TAP_LENGTH] = +{ + -0.001442203300285281, + 0.0, + 0.013017512802724852, + 0.0, + -0.061653278604903369, + 0.0, + 0.30007792316024057, + 0.5, + 0.30007792316024057, + 0.0, + -0.061653278604903369, + 0.0, + 0.013017512802724852, + 0.0, + -0.001442203300285281 +}; + +#define HB19TAP_LENGTH 19 +const TYPEREAL HB19TAP_H[HB19TAP_LENGTH] = +{ + 0.00042366527106480427, + 0.0, + -0.0040717333369021894, + 0.0, + 0.019895653881950692, + 0.0, + -0.070740034412329067, + 0.0, + 0.30449249772844139, + 0.5, + 0.30449249772844139, + 0.0, + -0.070740034412329067, + 0.0, + 0.019895653881950692, + 0.0, + -0.0040717333369021894, + 0.0, + 0.00042366527106480427 +}; + +#define HB23TAP_LENGTH 23 +const TYPEREAL HB23TAP_H[HB23TAP_LENGTH] = +{ + -0.00014987651418332164, + 0.0, + 0.0014748633283609852, + 0.0, + -0.0074416944990005314, + 0.0, + 0.026163522731980929, + 0.0, + -0.077593699116544707, + 0.0, + 0.30754683719791986, + 0.5, + 0.30754683719791986, + 0.0, + -0.077593699116544707, + 0.0, + 0.026163522731980929, + 0.0, + -0.0074416944990005314, + 0.0, + 0.0014748633283609852, + 0.0, + -0.00014987651418332164 +}; +#define HB27TAP_LENGTH 27 +const TYPEREAL HB27TAP_H[HB27TAP_LENGTH] = +{ + 0.000063730426952664685, + 0.0, + -0.00061985193978569082, + 0.0, + 0.0031512504783365756, + 0.0, + -0.011173151342856621, + 0.0, + 0.03171888754393197, + 0.0, + -0.082917863582770729, + 0.0, + 0.3097770473566307, + 0.5, + 0.3097770473566307, + 0.0, + -0.082917863582770729, + 0.0, + 0.03171888754393197, + 0.0, + -0.011173151342856621, + 0.0, + 0.0031512504783365756, + 0.0, + -0.00061985193978569082, + 0.0, + 0.000063730426952664685 +}; + +#define HB31TAP_LENGTH 31 +const TYPEREAL HB31TAP_H[HB31TAP_LENGTH] = +{ + -0.000030957335326552226, + 0.0, + 0.00029271992847303054, + 0.0, + -0.0014770381124258423, + 0.0, + 0.0052539088990950535, + 0.0, + -0.014856378748476874, + 0.0, + 0.036406651919555999, + 0.0, + -0.08699862567952929, + 0.0, + 0.31140967076042625, + 0.5, + 0.31140967076042625, + 0.0, + -0.08699862567952929, + 0.0, + 0.036406651919555999, + 0.0, + -0.014856378748476874, + 0.0, + 0.0052539088990950535, + 0.0, + -0.0014770381124258423, + 0.0, + 0.00029271992847303054, + 0.0, + -0.000030957335326552226 +}; +#define HB35TAP_LENGTH 35 +const TYPEREAL HB35TAP_H[HB35TAP_LENGTH] = +{ + 0.000017017718072971716, + 0.0, + -0.00015425042851962818, + 0.0, + 0.00076219685751140838, + 0.0, + -0.002691614694785393, + 0.0, + 0.0075927497927344764, + 0.0, + -0.018325727896057686, + 0.0, + 0.040351004914363969, + 0.0, + -0.090198224668969554, + 0.0, + 0.31264689763504327, + 0.5, + 0.31264689763504327, + 0.0, + -0.090198224668969554, + 0.0, + 0.040351004914363969, + 0.0, + -0.018325727896057686, + 0.0, + 0.0075927497927344764, + 0.0, + -0.002691614694785393, + 0.0, + 0.00076219685751140838, + 0.0, + -0.00015425042851962818, + 0.0, + 0.000017017718072971716 +}; +#define HB39TAP_LENGTH 39 +const TYPEREAL HB39TAP_H[HB39TAP_LENGTH] = +{ + -0.000010175082832074367, + 0.0, + 0.000088036416015024345, + 0.0, + -0.00042370835558387595, + 0.0, + 0.0014772557414459019, + 0.0, + -0.0041468438954260153, + 0.0, + 0.0099579126901608011, + 0.0, + -0.021433527104289002, + 0.0, + 0.043598963493432855, + 0.0, + -0.092695953625928404, + 0.0, + 0.31358799113382152, + 0.5, + 0.31358799113382152, + 0, + -0.092695953625928404, + 0.0, + 0.043598963493432855, + 0.0, + -0.021433527104289002, + 0.0, + 0.0099579126901608011, + 0.0, + -0.0041468438954260153, + 0.0, + 0.0014772557414459019, + 0.0, + -0.00042370835558387595, + 0.0, + 0.000088036416015024345, + 0.0, + -0.000010175082832074367 +}; +#define HB43TAP_LENGTH 43 +const TYPEREAL HB43TAP_H[HB43TAP_LENGTH] = +{ + 0.0000067666739082756387, + 0.0, + -0.000055275221547958285, + 0.0, + 0.00025654074579418561, + 0.0, + -0.0008748125689163153, + 0.0, + 0.0024249876017061502, + 0.0, + -0.0057775190656021748, + 0.0, + 0.012299834239523121, + 0.0, + -0.024244050662087069, + 0.0, + 0.046354303503099069, + 0.0, + -0.094729903598633314, + 0.0, + 0.31433918020123208, + 0.5, + 0.31433918020123208, + 0.0, + -0.094729903598633314, + 0.0, + 0.046354303503099069, + 0.0, + -0.024244050662087069, + 0.0, + 0.012299834239523121, + 0.0, + -0.0057775190656021748, + 0.0, + 0.0024249876017061502, + 0.0, + -0.0008748125689163153, + 0.0, + 0.00025654074579418561, + 0.0, + -0.000055275221547958285, + 0.0, + 0.0000067666739082756387 +}; +#define HB47TAP_LENGTH 47 +const TYPEREAL HB47TAP_H[HB47TAP_LENGTH] = +{ + -0.0000045298314172004251, + 0.0, + 0.000035333704512843228, + 0.0, + -0.00015934776420643447, + 0.0, + 0.0005340788063118928, + 0.0, + -0.0014667949695500761, + 0.0, + 0.0034792089350833247, + 0.0, + -0.0073794356720317733, + 0.0, + 0.014393786384683398, + 0.0, + -0.026586603160193314, + 0.0, + 0.048538673667907428, + 0.0, + -0.09629115286535718, + 0.0, + 0.31490673428547367, + 0.5, + 0.31490673428547367, + 0.0, + -0.09629115286535718, + 0.0, + 0.048538673667907428, + 0.0, + -0.026586603160193314, + 0.0, + 0.014393786384683398, + 0.0, + -0.0073794356720317733, + 0.0, + 0.0034792089350833247, + 0.0, + -0.0014667949695500761, + 0.0, + 0.0005340788063118928, + 0.0, + -0.00015934776420643447, + 0.0, + 0.000035333704512843228, + 0.0, + -0.0000045298314172004251 +}; +#define HB51TAP_LENGTH 51 +const TYPEREAL HB51TAP_H[HB51TAP_LENGTH] = +{ + 0.0000033359253688981639, + 0.0, + -0.000024584155158361803, + 0.0, + 0.00010677777483317733, + 0.0, + -0.00034890723143173914, + 0.0, + 0.00094239127078189603, + 0.0, + -0.0022118302078923137, + 0.0, + 0.0046575030752162277, + 0.0, + -0.0090130973415220566, + 0.0, + 0.016383673864361164, + 0.0, + -0.028697281101743237, + 0.0, + 0.05043292242400841, + 0.0, + -0.097611898315791965, + 0.0, + 0.31538104435015801, + 0.5, + 0.31538104435015801, + 0.0, + -0.097611898315791965, + 0.0, + 0.05043292242400841, + 0.0, + -0.028697281101743237, + 0.0, + 0.016383673864361164, + 0.0, + -0.0090130973415220566, + 0.0, + 0.0046575030752162277, + 0.0, + -0.0022118302078923137, + 0.0, + 0.00094239127078189603, + 0.0, + -0.00034890723143173914, + 0.0, + 0.00010677777483317733, + 0.0, + -0.000024584155158361803, + 0.0, + 0.0000033359253688981639 +}; + +#endif // FILTERCOEF_H + diff --git a/src/fmdsp/fir.cpp b/src/fmdsp/fir.cpp index fc47a34..5543618 100644 --- a/src/fmdsp/fir.cpp +++ b/src/fmdsp/fir.cpp @@ -1,567 +1,567 @@ -////////////////////////////////////////////////////////////////////// -// fir.cpp: implementation of the CFir class. -// -// This class implements a FIR filter using a dual flat coefficient -//array to eliminate testing for buffer wrap around. -// -//Filter coefficients can be from a fixed table or this class will create -// a lowpass or highpass filter from frequency and attenuation specifications -// using a Kaiser-Bessel windowed sinc algorithm -// -// History: -// 2011-01-29 Initial creation MSW -// 2011-03-27 Initial release -// 2011-08-07 Modified FIR filter initialization to force fixed size -// 2013-07-28 Added single/double precision math macros -////////////////////////////////////////////////////////////////////// - -//========================================================================================== -// + + + This Software is released under the "Simplified BSD License" + + + -//Copyright 2010 Moe Wheatley. All rights reserved. -// -//Redistribution and use in source and binary forms, with or without modification, are -//permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other materials -// provided with the distribution. -// -//THIS SOFTWARE IS PROVIDED BY Moe Wheatley ``AS IS'' AND ANY EXPRESS OR IMPLIED -//WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -//FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Moe Wheatley OR -//CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -//CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -//SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -//ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -//NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -//ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -//The views and conclusions contained in the software and documentation are those of the -//authors and should not be interpreted as representing official policies, either expressed -//or implied, of Moe Wheatley. -//========================================================================================== -#include "fir.h" - -////////////////////////////////////////////////////////////////////// -// Local Defines -////////////////////////////////////////////////////////////////////// -#define MAX_HALF_BAND_BUFSIZE 8192 - - -///////////////////////////////////////////////////////////////////////////////// -// Construct CFir object -///////////////////////////////////////////////////////////////////////////////// -CFir::CFir() -{ - m_NumTaps = 1; - m_State = 0; -} - - -///////////////////////////////////////////////////////////////////////////////// -// Process InLength InBuf[] samples and place in OutBuf[] -// Note the Coefficient array is twice the length and has a duplicated set -// in order to eliminate testing for buffer wrap in the inner loop -// ex: if 3 tap FIR with coefficients{21,-43,15} is made into a array of 6 entries -// {21, -43, 15, 21, -43, 15 } -//REAL version -///////////////////////////////////////////////////////////////////////////////// -void CFir::ProcessFilter(int InLength, TYPEREAL* InBuf, TYPEREAL* OutBuf) -{ -TYPEREAL acc; -TYPEREAL* Zptr; -const TYPEREAL* Hptr; - -#ifdef FMDSP_THREAD_SAFE - std::unique_lock lock(m_Mutex); -#endif - - for(int i=0; i lock(m_Mutex); -#endif - - for(int i=0; i lock(m_Mutex); -#endif - - for(int i=0; i lock(m_Mutex); -#endif - - m_SampleRate = Fsamprate; - if(NumTaps>MAX_NUMCOEF) - m_NumTaps = MAX_NUMCOEF; - else - m_NumTaps = NumTaps; - for(int i=0; i lock(m_Mutex); -#endif - - m_SampleRate = Fsamprate; - if(NumTaps>MAX_NUMCOEF) - m_NumTaps = MAX_NUMCOEF; - else - m_NumTaps = NumTaps; - for(int i=0; i lock(m_Mutex); -#endif - - m_SampleRate = Fsamprate; - //create normalized frequency parameters - TYPEREAL normFpass = Fpass/Fsamprate; - TYPEREAL normFstop = Fstop/Fsamprate; - TYPEREAL normFcut = (normFstop + normFpass)/2.0; //low pass filter 6dB cutoff - - //calculate Kaiser-Bessel window shape factor, Beta, from stopband attenuation - if(Astop < 20.96) - Beta = 0; - else if(Astop >= 50.0) - Beta = .1102 * (Astop - 8.71); - else - Beta = .5842 * MPOW( (Astop-20.96), 0.4) + .07886 * (Astop - 20.96); - - //Now Estimate number of filter taps required based on filter specs - m_NumTaps = static_cast((Astop - 8.0) / (2.285*K_2PI*(normFstop - normFpass) ) + 1); - - //clamp range of filter taps - if(m_NumTaps > MAX_NUMCOEF ) - m_NumTaps = MAX_NUMCOEF; - if(m_NumTaps < 3) - m_NumTaps = 3; - - if(NumTaps) //if need to force to to a number of taps - m_NumTaps = NumTaps; - - TYPEREAL fCenter = .5*(TYPEREAL)(m_NumTaps-1); - TYPEREAL izb = Izero(Beta); //precalculate denominator since is same for all points - for( n=0; n < m_NumTaps; n++) - { - TYPEREAL x = (TYPEREAL)n - fCenter; - TYPEREAL c; - // create ideal Sinc() LP filter with normFcut - if( (TYPEREAL)n == fCenter ) //deal with odd size filter singularity where sin(0)/0==1 - c = 2.0 * normFcut; - else - c = MSIN(K_2PI*x*normFcut)/(K_PI*x); - //calculate Kaiser window and multiply to get coefficient - x = ((TYPEREAL)n - ((TYPEREAL)m_NumTaps-1.0)/2.0 ) / (((TYPEREAL)m_NumTaps-1.0)/2.0); - m_Coef[n] = Scale * c * Izero( Beta * MSQRT(1 - (x*x) ) ) / izb; - } - - //make a 2x length array for FIR flat calculation efficiency - for (n = 0; n < m_NumTaps; n++) - m_Coef[n+m_NumTaps] = m_Coef[n]; - - //copy into complex coef buffers - for (n = 0; n < m_NumTaps*2; n++) - { - m_ICoef[n] = m_Coef[n]; - m_QCoef[n] = m_Coef[n]; - } - - //Initialize the FIR buffers and state - for(int i=0; i lock(m_Mutex); -#endif - - m_SampleRate = Fsamprate; - //create normalized frequency parameters - TYPEREAL normFpass = Fpass/Fsamprate; - TYPEREAL normFstop = Fstop/Fsamprate; - TYPEREAL normFcut = (normFstop + normFpass)/2.0; //high pass filter 6dB cutoff - - //calculate Kaiser-Bessel window shape factor, Beta, from stopband attenuation - if(Astop < 20.96) - Beta = 0; - else if(Astop >= 50.0) - Beta = .1102 * (Astop - 8.71); - else - Beta = .5842 * MPOW( (Astop-20.96), 0.4) + .07886 * (Astop - 20.96); - - //Now Estimate number of filter taps required based on filter specs - m_NumTaps = static_cast((Astop - 8.0) / (2.285*K_2PI*(normFpass - normFstop ) ) + 1); - - //clamp range of filter taps - if(m_NumTaps>(MAX_NUMCOEF-1) ) - m_NumTaps = MAX_NUMCOEF-1; - if(m_NumTaps < 3) - m_NumTaps = 3; - - m_NumTaps |= 1; //force to next odd number - - if(NumTaps) //if need to force to to a number of taps - m_NumTaps = NumTaps; - - TYPEREAL izb = Izero(Beta); //precalculate denominator since is same for all points - TYPEREAL fCenter = .5*(TYPEREAL)(m_NumTaps-1); - for( n=0; n < m_NumTaps; n++) - { - TYPEREAL x = (TYPEREAL)n - (TYPEREAL)(m_NumTaps-1)/2.0; - TYPEREAL c; - // create ideal Sinc() HP filter with normFcut - if( (TYPEREAL)n == fCenter ) //deal with odd size filter singularity where sin(0)/0==1 - c = 1.0 - 2.0 * normFcut; - else - c = MSIN(K_PI*x)/(K_PI*x) - MSIN(K_2PI*x*normFcut)/(K_PI*x); - - //calculate Kaiser window and multiply to get coefficient - x = ((TYPEREAL)n - ((TYPEREAL)m_NumTaps-1.0)/2.0 ) / (((TYPEREAL)m_NumTaps-1.0)/2.0); - m_Coef[n] = Scale * c * Izero( Beta * MSQRT(1 - (x*x) ) ) / izb; - } - - //make a 2x length array for FIR flat calculation efficiency - for (n = 0; n < m_NumTaps; n++) - m_Coef[n+m_NumTaps] = m_Coef[n]; - - //copy into complex coef buffers - for (n = 0; n < m_NumTaps*2; n++) - { - m_ICoef[n] = m_Coef[n]; - m_QCoef[n] = m_Coef[n]; - } - - //Initialize the FIR buffers and state - for(int i=0; i [(x/2)^k / k!]^2 } -/////////////////////////////////////////////////////////////////////////// -TYPEREAL CFir::Izero(TYPEREAL x) -{ -TYPEREAL x2 = x/2.0; -TYPEREAL sum = 1.0; -TYPEREAL ds = 1.0; -TYPEREAL di = 1.0; -TYPEREAL errorlimit = 1e-9; -TYPEREAL tmp; - do - { - tmp = x2/di; - tmp *= tmp; - ds *= tmp; - sum += ds; - di += 1.0; - }while(ds >= errorlimit*sum); - - return(sum); -} - - -// *&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&* -// Class to do decimation by 2 -// *&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&* - -////////////////////////////////////////////////////////////////////// -//Decimate by 2 Halfband filter class implementation -////////////////////////////////////////////////////////////////////// -CDecimateBy2::CDecimateBy2(int len,const TYPEREAL* pCoef ) - : m_FirLength(len), m_pCoef(pCoef) -{ - //create buffer for FIR implementation - m_pHBFirRBuf = new TYPEREAL[MAX_HALF_BAND_BUFSIZE]; - m_pHBFirCBuf = new TYPECPX[MAX_HALF_BAND_BUFSIZE]; - TYPECPX CPXZERO = {0.0,0.0}; - for(int i=0; i lock(m_Mutex); +#endif + + for(int i=0; i lock(m_Mutex); +#endif + + for(int i=0; i lock(m_Mutex); +#endif + + for(int i=0; i lock(m_Mutex); +#endif + + m_SampleRate = Fsamprate; + if(NumTaps>MAX_NUMCOEF) + m_NumTaps = MAX_NUMCOEF; + else + m_NumTaps = NumTaps; + for(int i=0; i lock(m_Mutex); +#endif + + m_SampleRate = Fsamprate; + if(NumTaps>MAX_NUMCOEF) + m_NumTaps = MAX_NUMCOEF; + else + m_NumTaps = NumTaps; + for(int i=0; i lock(m_Mutex); +#endif + + m_SampleRate = Fsamprate; + //create normalized frequency parameters + TYPEREAL normFpass = Fpass/Fsamprate; + TYPEREAL normFstop = Fstop/Fsamprate; + TYPEREAL normFcut = (normFstop + normFpass)/2.0; //low pass filter 6dB cutoff + + //calculate Kaiser-Bessel window shape factor, Beta, from stopband attenuation + if(Astop < 20.96) + Beta = 0; + else if(Astop >= 50.0) + Beta = .1102 * (Astop - 8.71); + else + Beta = .5842 * MPOW( (Astop-20.96), 0.4) + .07886 * (Astop - 20.96); + + //Now Estimate number of filter taps required based on filter specs + m_NumTaps = static_cast((Astop - 8.0) / (2.285*K_2PI*(normFstop - normFpass) ) + 1); + + //clamp range of filter taps + if(m_NumTaps > MAX_NUMCOEF ) + m_NumTaps = MAX_NUMCOEF; + if(m_NumTaps < 3) + m_NumTaps = 3; + + if(NumTaps) //if need to force to to a number of taps + m_NumTaps = NumTaps; + + TYPEREAL fCenter = .5*(TYPEREAL)(m_NumTaps-1); + TYPEREAL izb = Izero(Beta); //precalculate denominator since is same for all points + for( n=0; n < m_NumTaps; n++) + { + TYPEREAL x = (TYPEREAL)n - fCenter; + TYPEREAL c; + // create ideal Sinc() LP filter with normFcut + if( (TYPEREAL)n == fCenter ) //deal with odd size filter singularity where sin(0)/0==1 + c = 2.0 * normFcut; + else + c = MSIN(K_2PI*x*normFcut)/(K_PI*x); + //calculate Kaiser window and multiply to get coefficient + x = ((TYPEREAL)n - ((TYPEREAL)m_NumTaps-1.0)/2.0 ) / (((TYPEREAL)m_NumTaps-1.0)/2.0); + m_Coef[n] = Scale * c * Izero( Beta * MSQRT(1 - (x*x) ) ) / izb; + } + + //make a 2x length array for FIR flat calculation efficiency + for (n = 0; n < m_NumTaps; n++) + m_Coef[n+m_NumTaps] = m_Coef[n]; + + //copy into complex coef buffers + for (n = 0; n < m_NumTaps*2; n++) + { + m_ICoef[n] = m_Coef[n]; + m_QCoef[n] = m_Coef[n]; + } + + //Initialize the FIR buffers and state + for(int i=0; i lock(m_Mutex); +#endif + + m_SampleRate = Fsamprate; + //create normalized frequency parameters + TYPEREAL normFpass = Fpass/Fsamprate; + TYPEREAL normFstop = Fstop/Fsamprate; + TYPEREAL normFcut = (normFstop + normFpass)/2.0; //high pass filter 6dB cutoff + + //calculate Kaiser-Bessel window shape factor, Beta, from stopband attenuation + if(Astop < 20.96) + Beta = 0; + else if(Astop >= 50.0) + Beta = .1102 * (Astop - 8.71); + else + Beta = .5842 * MPOW( (Astop-20.96), 0.4) + .07886 * (Astop - 20.96); + + //Now Estimate number of filter taps required based on filter specs + m_NumTaps = static_cast((Astop - 8.0) / (2.285*K_2PI*(normFpass - normFstop ) ) + 1); + + //clamp range of filter taps + if(m_NumTaps>(MAX_NUMCOEF-1) ) + m_NumTaps = MAX_NUMCOEF-1; + if(m_NumTaps < 3) + m_NumTaps = 3; + + m_NumTaps |= 1; //force to next odd number + + if(NumTaps) //if need to force to to a number of taps + m_NumTaps = NumTaps; + + TYPEREAL izb = Izero(Beta); //precalculate denominator since is same for all points + TYPEREAL fCenter = .5*(TYPEREAL)(m_NumTaps-1); + for( n=0; n < m_NumTaps; n++) + { + TYPEREAL x = (TYPEREAL)n - (TYPEREAL)(m_NumTaps-1)/2.0; + TYPEREAL c; + // create ideal Sinc() HP filter with normFcut + if( (TYPEREAL)n == fCenter ) //deal with odd size filter singularity where sin(0)/0==1 + c = 1.0 - 2.0 * normFcut; + else + c = MSIN(K_PI*x)/(K_PI*x) - MSIN(K_2PI*x*normFcut)/(K_PI*x); + + //calculate Kaiser window and multiply to get coefficient + x = ((TYPEREAL)n - ((TYPEREAL)m_NumTaps-1.0)/2.0 ) / (((TYPEREAL)m_NumTaps-1.0)/2.0); + m_Coef[n] = Scale * c * Izero( Beta * MSQRT(1 - (x*x) ) ) / izb; + } + + //make a 2x length array for FIR flat calculation efficiency + for (n = 0; n < m_NumTaps; n++) + m_Coef[n+m_NumTaps] = m_Coef[n]; + + //copy into complex coef buffers + for (n = 0; n < m_NumTaps*2; n++) + { + m_ICoef[n] = m_Coef[n]; + m_QCoef[n] = m_Coef[n]; + } + + //Initialize the FIR buffers and state + for(int i=0; i [(x/2)^k / k!]^2 } +/////////////////////////////////////////////////////////////////////////// +TYPEREAL CFir::Izero(TYPEREAL x) +{ +TYPEREAL x2 = x/2.0; +TYPEREAL sum = 1.0; +TYPEREAL ds = 1.0; +TYPEREAL di = 1.0; +TYPEREAL errorlimit = 1e-9; +TYPEREAL tmp; + do + { + tmp = x2/di; + tmp *= tmp; + ds *= tmp; + sum += ds; + di += 1.0; + }while(ds >= errorlimit*sum); + + return(sum); +} + + +// *&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&* +// Class to do decimation by 2 +// *&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&* + +////////////////////////////////////////////////////////////////////// +//Decimate by 2 Halfband filter class implementation +////////////////////////////////////////////////////////////////////// +CDecimateBy2::CDecimateBy2(int len,const TYPEREAL* pCoef ) + : m_FirLength(len), m_pCoef(pCoef) +{ + //create buffer for FIR implementation + m_pHBFirRBuf = new TYPEREAL[MAX_HALF_BAND_BUFSIZE]; + m_pHBFirCBuf = new TYPECPX[MAX_HALF_BAND_BUFSIZE]; + TYPECPX CPXZERO = {0.0,0.0}; + for(int i=0; i - -//////////// -//class for FIR Filters -//////////// -class CFir -{ -public: - CFir(); - - void InitConstFir( int NumTaps, const TYPEREAL* pCoef, TYPEREAL Fsamprate); - void InitConstFir( int NumTaps, const TYPEREAL* pICoef, const TYPEREAL* pQCoef, TYPEREAL Fsamprate); - int InitLPFilter(int NumTaps, TYPEREAL Scale, TYPEREAL Astop, TYPEREAL Fpass, TYPEREAL Fstop, TYPEREAL Fsamprate); - int InitHPFilter(int NumTaps, TYPEREAL Scale, TYPEREAL Astop, TYPEREAL Fpass, TYPEREAL Fstop, TYPEREAL Fsamprate); - void GenerateHBFilter( TYPEREAL FreqOffset); - void ProcessFilter(int InLength, TYPEREAL* InBuf, TYPEREAL* OutBuf); - void ProcessFilter(int InLength, TYPEREAL* InBuf, TYPECPX* OutBuf); - void ProcessFilter(int InLength, TYPECPX* InBuf, TYPECPX* OutBuf); - -private: - TYPEREAL Izero(TYPEREAL x); - TYPEREAL m_SampleRate; - int m_NumTaps; - int m_State; - TYPEREAL m_Coef[MAX_NUMCOEF*2]; - TYPEREAL m_ICoef[MAX_NUMCOEF*2]; - TYPEREAL m_QCoef[MAX_NUMCOEF*2]; - TYPEREAL m_rZBuf[MAX_NUMCOEF]; - TYPECPX m_cZBuf[MAX_NUMCOEF]; -#ifdef FMDSP_THREAD_SAFE - mutable std::mutex m_Mutex; //for keeping threads from stomping on each other -#endif -}; - -//////////// -//class for the Half Band decimate by 2 FIR filters -//////////// -class CDecimateBy2 -{ -public: - CDecimateBy2(int len, const TYPEREAL* pCoef); - ~CDecimateBy2(){if(m_pHBFirRBuf) delete m_pHBFirRBuf; if(m_pHBFirCBuf) delete m_pHBFirCBuf;} - int DecBy2(int InLength, TYPEREAL* pInData, TYPEREAL* pOutData); - int DecBy2(int InLength, TYPECPX* pInData, TYPECPX* pOutData); - TYPEREAL* m_pHBFirRBuf; - TYPECPX* m_pHBFirCBuf; - int m_FirLength; - const TYPEREAL* m_pCoef; -}; - -#endif // FIR_H +////////////////////////////////////////////////////////////////////// +// fir.h: interface for the CFir class. +// +// This class implements a FIR filter using a dual flat coefficient +//array to eliminate testing for buffer wrap around. +// +//Also a decimate by 3 half band filter class CDecimateBy2 is implemented +// +// History: +// 2011-01-29 Initial creation MSW +// 2011-03-27 Initial release +// 2011-08-05 Added decimate by 2 class +// 2011-08-07 Modified FIR filter initialization +////////////////////////////////////////////////////////////////////// +//========================================================================================== +// + + + This Software is released under the "Simplified BSD License" + + + +//Copyright 2010 Moe Wheatley. All rights reserved. +// +//Redistribution and use in source and binary forms, with or without modification, are +//permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other materials +// provided with the distribution. +// +//THIS SOFTWARE IS PROVIDED BY Moe Wheatley ``AS IS'' AND ANY EXPRESS OR IMPLIED +//WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +//FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Moe Wheatley OR +//CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +//CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +//SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +//ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +//NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +//ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +//The views and conclusions contained in the software and documentation are those of the +//authors and should not be interpreted as representing official policies, either expressed +//or implied, of Moe Wheatley. +//============================================================================= +#ifndef FIR_H +#define FIR_H + +#include "datatypes.h" +#include "filtercoef.h" + +#define MAX_NUMCOEF 75 +#include + +//////////// +//class for FIR Filters +//////////// +class CFir +{ +public: + CFir(); + + void InitConstFir( int NumTaps, const TYPEREAL* pCoef, TYPEREAL Fsamprate); + void InitConstFir( int NumTaps, const TYPEREAL* pICoef, const TYPEREAL* pQCoef, TYPEREAL Fsamprate); + int InitLPFilter(int NumTaps, TYPEREAL Scale, TYPEREAL Astop, TYPEREAL Fpass, TYPEREAL Fstop, TYPEREAL Fsamprate); + int InitHPFilter(int NumTaps, TYPEREAL Scale, TYPEREAL Astop, TYPEREAL Fpass, TYPEREAL Fstop, TYPEREAL Fsamprate); + void GenerateHBFilter( TYPEREAL FreqOffset); + void ProcessFilter(int InLength, TYPEREAL* InBuf, TYPEREAL* OutBuf); + void ProcessFilter(int InLength, TYPEREAL* InBuf, TYPECPX* OutBuf); + void ProcessFilter(int InLength, TYPECPX* InBuf, TYPECPX* OutBuf); + +private: + TYPEREAL Izero(TYPEREAL x); + TYPEREAL m_SampleRate; + int m_NumTaps; + int m_State; + TYPEREAL m_Coef[MAX_NUMCOEF*2]; + TYPEREAL m_ICoef[MAX_NUMCOEF*2]; + TYPEREAL m_QCoef[MAX_NUMCOEF*2]; + TYPEREAL m_rZBuf[MAX_NUMCOEF]; + TYPECPX m_cZBuf[MAX_NUMCOEF]; +#ifdef FMDSP_THREAD_SAFE + mutable std::mutex m_Mutex; //for keeping threads from stomping on each other +#endif +}; + +//////////// +//class for the Half Band decimate by 2 FIR filters +//////////// +class CDecimateBy2 +{ +public: + CDecimateBy2(int len, const TYPEREAL* pCoef); + ~CDecimateBy2(){if(m_pHBFirRBuf) delete m_pHBFirRBuf; if(m_pHBFirCBuf) delete m_pHBFirCBuf;} + int DecBy2(int InLength, TYPEREAL* pInData, TYPEREAL* pOutData); + int DecBy2(int InLength, TYPECPX* pInData, TYPECPX* pOutData); + TYPEREAL* m_pHBFirRBuf; + TYPECPX* m_pHBFirCBuf; + int m_FirLength; + const TYPEREAL* m_pCoef; +}; + +#endif // FIR_H diff --git a/src/fmdsp/fmdemod.cpp b/src/fmdsp/fmdemod.cpp index 4dcdeb8..5203019 100644 --- a/src/fmdsp/fmdemod.cpp +++ b/src/fmdsp/fmdemod.cpp @@ -1,268 +1,268 @@ -// fmdemod.cpp: implementation of the CFmDemod class. -// -// This class takes I/Q baseband data and performs -// FM demodulation and squelch -// -// History: -// 2011-01-17 Initial creation MSW -// 2011-03-27 Initial release -// 2011-08-07 Modified FIR filter initialization to force fixed size -// 2013-07-28 Added single/double precision math macros -////////////////////////////////////////////////////////////////////// - -//========================================================================================== -// + + + This Software is released under the "Simplified BSD License" + + + -//Copyright 2010 Moe Wheatley. All rights reserved. -// -//Redistribution and use in source and binary forms, with or without modification, are -//permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other materials -// provided with the distribution. -// -//THIS SOFTWARE IS PROVIDED BY Moe Wheatley ``AS IS'' AND ANY EXPRESS OR IMPLIED -//WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -//FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Moe Wheatley OR -//CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -//CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -//SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -//ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -//NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -//ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -//The views and conclusions contained in the software and documentation are those of the -//authors and should not be interpreted as representing official policies, either expressed -//or implied, of Moe Wheatley. -//========================================================================================== -#include "fmdemod.h" -#include "datatypes.h" - - -#define FMPLL_RANGE 15000.0 //maximum deviation limit of PLL -#define VOICE_BANDWIDTH 2500.0 //3000.0 - -#define FMPLL_BW VOICE_BANDWIDTH //natural frequency ~loop bandwidth -#define FMPLL_ZETA .707 //PLL Loop damping factor - -#define FMDC_ALPHA 0.001 //time constant for DC removal filter - -#define MAX_FMOUT 100000.0 - -#define SQUELCH_MAX 8000.0 //roughly the maximum noise average with no signal -#define SQUELCHAVE_TIMECONST .02 -#define SQUELCH_HYSTERESIS 50.0 - -#define DEMPHASIS_TIME 75e-6 //80e-6 - -///////////////////////////////////////////////////////////////////////////////// -// Construct FM demod object -///////////////////////////////////////////////////////////////////////////////// -CFmDemod::CFmDemod(TYPEREAL samplerate) : m_SampleRate(samplerate) -{ - m_FreqErrorDC = 0.0; - m_NcoPhase = 0.0; - m_NcoFreq = 0.0; - - SetSampleRate(m_SampleRate); -} - - -///////////////////////////////////////////////////////////////////////////////// -// Sets sample rate and adjusts any parameters that are affected. -///////////////////////////////////////////////////////////////////////////////// -void CFmDemod::SetSampleRate(TYPEREAL samplerate) -{ - m_SampleRate = samplerate; - - TYPEREAL norm = K_2PI/m_SampleRate; //to normalize Hz to radians - - //initialize the PLL - m_NcoLLimit = -FMPLL_RANGE * norm; //clamp FM PLL NCO - m_NcoHLimit = FMPLL_RANGE * norm; - m_PllAlpha = 2.0*FMPLL_ZETA*FMPLL_BW * norm; - m_PllBeta = (m_PllAlpha * m_PllAlpha)/(4.0*FMPLL_ZETA*FMPLL_ZETA); - - m_OutGain = MAX_FMOUT/m_NcoHLimit; //audio output level gain value - - //DC removal filter time constant - m_DcAlpha = (1.0 - MEXP(-1.0/(m_SampleRate*FMDC_ALPHA)) ); - - //initialize some noise squelch items - m_SquelchHPFreq = VOICE_BANDWIDTH; - m_SquelchAve = 0.0; - m_SquelchState = true; - m_SquelchAlpha = (1.0-MEXP(-1.0/(m_SampleRate*SQUELCHAVE_TIMECONST)) ); - - m_DeemphasisAlpha = (1.0-MEXP(-1.0/(m_SampleRate*DEMPHASIS_TIME)) ); - m_DeemphasisAve = 0.0; - - //m_LpFir.InitLPFilter(0, 1.0, 50.0, VOICE_BANDWIDTH, 1.6 * VOICE_BANDWIDTH, m_SampleRate); - m_LpFir.InitLPFilter(0, 1.0, 50.0, VOICE_BANDWIDTH, 2.0 * VOICE_BANDWIDTH, m_SampleRate); - - InitNoiseSquelch(); -} - - -///////////////////////////////////////////////////////////////////////////////// -// Sets squelch threshold based on 'Value' which goes from -160 to 0. -///////////////////////////////////////////////////////////////////////////////// -void CFmDemod::SetSquelch(int Value) -{ - m_SquelchThreshold = (SQUELCH_MAX*(TYPEREAL)Value)/-160.0; -} - - -///////////////////////////////////////////////////////////////////////////////// -// Sets up Highpass noise filter parameters based on input filter BW -///////////////////////////////////////////////////////////////////////////////// -void CFmDemod::InitNoiseSquelch() -{ - //m_HpFir.InitHPFilter(0, 1.0, 50.0, m_SquelchHPFreq*.8, m_SquelchHPFreq*.65, m_SampleRate); - m_HpFir.InitHPFilter(0, 1.0, 50.0, VOICE_BANDWIDTH*2.0, VOICE_BANDWIDTH, m_SampleRate); -} - - -///////////////////////////////////////////////////////////////////////////////// -// Performs noise squelch by reading the noise power above the voice frequencies -///////////////////////////////////////////////////////////////////////////////// -void CFmDemod::PerformNoiseSquelch(int InLength, TYPEREAL* pOutData) -{ - if(InLength>MAX_SQBUF_SIZE) - return; - TYPEREAL sqbuf[MAX_SQBUF_SIZE]; - //high pass filter to get the high frequency noise above the voice - m_HpFir.ProcessFilter(InLength, pOutData, sqbuf); - for(int i=0; i= (m_SquelchThreshold+SQUELCH_HYSTERESIS)) - m_SquelchState = true; - } -//m_SquelchState = false; - if(m_SquelchState) - { //zero output if squelched - for(int i=0; i m_NcoHLimit) - m_NcoFreq = m_NcoHLimit; - else if(m_NcoFreq < m_NcoLLimit) - m_NcoFreq = m_NcoLLimit; - //update NCO phase with new value - m_NcoPhase += (m_NcoFreq + m_PllAlpha * phzerror); - //LP filter the NCO frequency term to get DC offset value - m_FreqErrorDC = (1.0-m_DcAlpha)*m_FreqErrorDC + m_DcAlpha*m_NcoFreq; - //subtract out DC term to get FM audio - pOutData[i] = (m_NcoFreq-m_FreqErrorDC)*m_OutGain; - } - m_NcoPhase = MFMOD(m_NcoPhase, K_2PI); //keep radian counter bounded - PerformNoiseSquelch(InLength, pOutData); //calculate squelch - return InLength; -} - -///////////////////////////////////////////////////////////////////////////////// -// Process FM demod STEREO version -///////////////////////////////////////////////////////////////////////////////// -int CFmDemod::ProcessData(int InLength, TYPEREAL FmBW, TYPECPX* pInData, TYPECPX* pOutData) -{ -TYPECPX tmp; - if(m_SquelchHPFreq != FmBW) - { //update Squelch HP filter cutoff from main filter BW - m_SquelchHPFreq = FmBW; - InitNoiseSquelch(); - } - for(int i=0; i m_NcoHLimit) - m_NcoFreq = m_NcoHLimit; - else if(m_NcoFreq < m_NcoLLimit) - m_NcoFreq = m_NcoLLimit; - //update NCO phase with new value - m_NcoPhase += (m_NcoFreq + m_PllAlpha * phzerror); - //LP filter the NCO frequency term to get DC offset value - m_FreqErrorDC = (1.0-m_DcAlpha)*m_FreqErrorDC + m_DcAlpha*m_NcoFreq; - //subtract out DC term to get FM audio - m_OutBuf[i] = (m_NcoFreq-m_FreqErrorDC)*m_OutGain; - } - m_NcoPhase = MFMOD(m_NcoPhase, K_2PI); //keep radian counter bounded - PerformNoiseSquelch(InLength, m_OutBuf); - for(int i=0; iMAX_SQBUF_SIZE) + return; + TYPEREAL sqbuf[MAX_SQBUF_SIZE]; + //high pass filter to get the high frequency noise above the voice + m_HpFir.ProcessFilter(InLength, pOutData, sqbuf); + for(int i=0; i= (m_SquelchThreshold+SQUELCH_HYSTERESIS)) + m_SquelchState = true; + } +//m_SquelchState = false; + if(m_SquelchState) + { //zero output if squelched + for(int i=0; i m_NcoHLimit) + m_NcoFreq = m_NcoHLimit; + else if(m_NcoFreq < m_NcoLLimit) + m_NcoFreq = m_NcoLLimit; + //update NCO phase with new value + m_NcoPhase += (m_NcoFreq + m_PllAlpha * phzerror); + //LP filter the NCO frequency term to get DC offset value + m_FreqErrorDC = (1.0-m_DcAlpha)*m_FreqErrorDC + m_DcAlpha*m_NcoFreq; + //subtract out DC term to get FM audio + pOutData[i] = (m_NcoFreq-m_FreqErrorDC)*m_OutGain; + } + m_NcoPhase = MFMOD(m_NcoPhase, K_2PI); //keep radian counter bounded + PerformNoiseSquelch(InLength, pOutData); //calculate squelch + return InLength; +} + +///////////////////////////////////////////////////////////////////////////////// +// Process FM demod STEREO version +///////////////////////////////////////////////////////////////////////////////// +int CFmDemod::ProcessData(int InLength, TYPEREAL FmBW, TYPECPX* pInData, TYPECPX* pOutData) +{ +TYPECPX tmp; + if(m_SquelchHPFreq != FmBW) + { //update Squelch HP filter cutoff from main filter BW + m_SquelchHPFreq = FmBW; + InitNoiseSquelch(); + } + for(int i=0; i m_NcoHLimit) + m_NcoFreq = m_NcoHLimit; + else if(m_NcoFreq < m_NcoLLimit) + m_NcoFreq = m_NcoLLimit; + //update NCO phase with new value + m_NcoPhase += (m_NcoFreq + m_PllAlpha * phzerror); + //LP filter the NCO frequency term to get DC offset value + m_FreqErrorDC = (1.0-m_DcAlpha)*m_FreqErrorDC + m_DcAlpha*m_NcoFreq; + //subtract out DC term to get FM audio + m_OutBuf[i] = (m_NcoFreq-m_FreqErrorDC)*m_OutGain; + } + m_NcoPhase = MFMOD(m_NcoPhase, K_2PI); //keep radian counter bounded + PerformNoiseSquelch(InLength, m_OutBuf); + for(int i=0; i - -////////////////////////////////////////////////////////////////////// -// Local defines -////////////////////////////////////////////////////////////////////// -#ifdef FMDSP_USE_DOUBLE_PRECISION -#define SINC_PERIOD_PTS 10000 //number of points in sinc table between "zero crossings" - //smaller value increases noise floor -#define SINC_PERIODS 28 //number of input sample periods("zero crossings"-1) in - //sinc function(should be even) - //decreasing reduces alias free bandwidth -#else //if using single precision math assume lightweight CPU and dont worry so much about resample quality -#define SINC_PERIOD_PTS 1000 -#define SINC_PERIODS 10 -#endif - -#define SINC_LENGTH ( (SINC_PERIODS)*SINC_PERIOD_PTS + 1)//number of total points in sinc table - -#define MAX_SOUNDCARDVAL 32767.0 - - -////////////////////////////////////////////////////////////////////// -// Construction/Destruction -////////////////////////////////////////////////////////////////////// -CFractResampler::CFractResampler() -{ - m_pSinc = NULL; - m_pInputBuf = NULL; - -} - -CFractResampler::~CFractResampler() -{ - if(m_pSinc) - delete[] m_pSinc; - if(m_pInputBuf) - delete[] m_pInputBuf; -} - -////////////////////////////////////////////////////////////////////// -// Initialize resampler memory and create windowed sinc table -// MaxInputSize is the largest number of input samples expected to be processed -////////////////////////////////////////////////////////////////////// -void CFractResampler::Init(int MaxInputSize) -{ -int i; -TYPEREAL fi; -TYPEREAL window; - MaxInputSize += SINC_PERIODS; //expand buffer size to include wrap around - if(NULL == m_pSinc) - m_pSinc = new TYPEREAL[SINC_LENGTH]; - if(m_pInputBuf) - delete[] m_pInputBuf; - m_pInputBuf = new TYPECPX[MaxInputSize]; - for(i=0; i MAX_SOUNDCARDVAL) - tmp.re = MAX_SOUNDCARDVAL; - if(tmp.re < -MAX_SOUNDCARDVAL) - tmp.re = -MAX_SOUNDCARDVAL; - if(tmp.im>MAX_SOUNDCARDVAL) - tmp.im = MAX_SOUNDCARDVAL; - if(tmp.im < -MAX_SOUNDCARDVAL) - tmp.im = -MAX_SOUNDCARDVAL; - pOutBuf[outsamples].re = (qint16)tmp.re; - pOutBuf[outsamples++].im = (qint16)tmp.im; - - m_FloatTime += dt; //inc floating pt output time step - IntegerTime = (int)m_FloatTime; //truncate to integer - } - m_FloatTime -= (TYPEREAL)InLength; //move floating time position back for next call - //keeping leftover fraction - //need to copy last SINC_PERIODS input samples in buffer to beginning of buffer - // for FIR wrap around management. j points to last input sample processed - j = InLength; - for(i=0; i MAX_SOUNDCARDVAL) - tmp = MAX_SOUNDCARDVAL; - if(tmp < -MAX_SOUNDCARDVAL) - tmp = -MAX_SOUNDCARDVAL; - pOutBuf[outsamples++] = (TYPEMONO16)tmp; - - m_FloatTime += dt; - IntegerTime = (int)m_FloatTime; - } - m_FloatTime -= (TYPEREAL)InLength; //move floating time position back for next call - //keeping leftover fraction - //need to copy last SINC_PERIODS input samples in buffer to beginning of buffer - // for FIR wrap around management. j points to last input sample processed - j = InLength; - for(i=0; i + +////////////////////////////////////////////////////////////////////// +// Local defines +////////////////////////////////////////////////////////////////////// +#ifdef FMDSP_USE_DOUBLE_PRECISION +#define SINC_PERIOD_PTS 10000 //number of points in sinc table between "zero crossings" + //smaller value increases noise floor +#define SINC_PERIODS 28 //number of input sample periods("zero crossings"-1) in + //sinc function(should be even) + //decreasing reduces alias free bandwidth +#else //if using single precision math assume lightweight CPU and dont worry so much about resample quality +#define SINC_PERIOD_PTS 1000 +#define SINC_PERIODS 10 +#endif + +#define SINC_LENGTH ( (SINC_PERIODS)*SINC_PERIOD_PTS + 1)//number of total points in sinc table + +#define MAX_SOUNDCARDVAL 32767.0 + + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// +CFractResampler::CFractResampler() +{ + m_pSinc = NULL; + m_pInputBuf = NULL; + +} + +CFractResampler::~CFractResampler() +{ + if(m_pSinc) + delete[] m_pSinc; + if(m_pInputBuf) + delete[] m_pInputBuf; +} + +////////////////////////////////////////////////////////////////////// +// Initialize resampler memory and create windowed sinc table +// MaxInputSize is the largest number of input samples expected to be processed +////////////////////////////////////////////////////////////////////// +void CFractResampler::Init(int MaxInputSize) +{ +int i; +TYPEREAL fi; +TYPEREAL window; + MaxInputSize += SINC_PERIODS; //expand buffer size to include wrap around + if(NULL == m_pSinc) + m_pSinc = new TYPEREAL[SINC_LENGTH]; + if(m_pInputBuf) + delete[] m_pInputBuf; + m_pInputBuf = new TYPECPX[MaxInputSize]; + for(i=0; i MAX_SOUNDCARDVAL) + tmp.re = MAX_SOUNDCARDVAL; + if(tmp.re < -MAX_SOUNDCARDVAL) + tmp.re = -MAX_SOUNDCARDVAL; + if(tmp.im>MAX_SOUNDCARDVAL) + tmp.im = MAX_SOUNDCARDVAL; + if(tmp.im < -MAX_SOUNDCARDVAL) + tmp.im = -MAX_SOUNDCARDVAL; + pOutBuf[outsamples].re = (qint16)tmp.re; + pOutBuf[outsamples++].im = (qint16)tmp.im; + + m_FloatTime += dt; //inc floating pt output time step + IntegerTime = (int)m_FloatTime; //truncate to integer + } + m_FloatTime -= (TYPEREAL)InLength; //move floating time position back for next call + //keeping leftover fraction + //need to copy last SINC_PERIODS input samples in buffer to beginning of buffer + // for FIR wrap around management. j points to last input sample processed + j = InLength; + for(i=0; i MAX_SOUNDCARDVAL) + tmp = MAX_SOUNDCARDVAL; + if(tmp < -MAX_SOUNDCARDVAL) + tmp = -MAX_SOUNDCARDVAL; + pOutBuf[outsamples++] = (TYPEMONO16)tmp; + + m_FloatTime += dt; + IntegerTime = (int)m_FloatTime; + } + m_FloatTime -= (TYPEREAL)InLength; //move floating time position back for next call + //keeping leftover fraction + //need to copy last SINC_PERIODS input samples in buffer to beginning of buffer + // for FIR wrap around management. j points to last input sample processed + j = InLength; + for(i=0; i - -#include "datatypes.h" - -class CFractResampler -{ -public: - CFractResampler(); - virtual ~CFractResampler(); - - void Init(int MaxInputSize); - //overloaded functions for processing different data types - int Resample( int InLength, TYPEREAL Rate, TYPEREAL* pInBuf, TYPEREAL* pOutBuf); - int Resample( int InLength, TYPEREAL Rate, TYPECPX* pInBuf, TYPECPX* pOutBuf); - int Resample( int InLength, TYPEREAL Rate, TYPEREAL* pInBuf, TYPEMONO16* pOutBuf, TYPEREAL gain); - int Resample( int InLength, TYPEREAL Rate, TYPECPX* pInBuf, TYPESTEREO16* pOutBuf, TYPEREAL gain); - -private: - TYPEREAL m_FloatTime; //floating pt output time accumulator - TYPEREAL* m_pSinc; //ptr to sinc table - TYPECPX* m_pInputBuf; //internal working input sample buffer -}; - -#endif // FRACTRESAMPLER_H +////////////////////////////////////////////////////////////////////// +// fractresampler.h: interface for the CFractResampler class. +// +// This class implements a fractional resampler that can be used to +//convert between different sample rates +// +// History: +// 2010-09-15 Initial creation MSW +// 2011-03-27 Initial release +////////////////////////////////////////////////////////////////////// +//========================================================================================== +// + + + This Software is released under the "Simplified BSD License" + + + +//Copyright 2010 Moe Wheatley. All rights reserved. +// +//Redistribution and use in source and binary forms, with or without modification, are +//permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other materials +// provided with the distribution. +// +//THIS SOFTWARE IS PROVIDED BY Moe Wheatley ``AS IS'' AND ANY EXPRESS OR IMPLIED +//WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +//FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Moe Wheatley OR +//CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +//CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +//SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +//ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +//NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +//ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +//The views and conclusions contained in the software and documentation are those of the +//authors and should not be interpreted as representing official policies, either expressed +//or implied, of Moe Wheatley. +//============================================================================= +#ifndef FRACTRESAMPLER_H +#define FRACTRESAMPLER_H + +#include + +#include "datatypes.h" + +class CFractResampler +{ +public: + CFractResampler(); + virtual ~CFractResampler(); + + void Init(int MaxInputSize); + //overloaded functions for processing different data types + int Resample( int InLength, TYPEREAL Rate, TYPEREAL* pInBuf, TYPEREAL* pOutBuf); + int Resample( int InLength, TYPEREAL Rate, TYPECPX* pInBuf, TYPECPX* pOutBuf); + int Resample( int InLength, TYPEREAL Rate, TYPEREAL* pInBuf, TYPEMONO16* pOutBuf, TYPEREAL gain); + int Resample( int InLength, TYPEREAL Rate, TYPECPX* pInBuf, TYPESTEREO16* pOutBuf, TYPEREAL gain); + +private: + TYPEREAL m_FloatTime; //floating pt output time accumulator + TYPEREAL* m_pSinc; //ptr to sinc table + TYPECPX* m_pInputBuf; //internal working input sample buffer +}; + +#endif // FRACTRESAMPLER_H diff --git a/src/fmdsp/iir.cpp b/src/fmdsp/iir.cpp index 1db34b6..6af7769 100644 --- a/src/fmdsp/iir.cpp +++ b/src/fmdsp/iir.cpp @@ -1,204 +1,204 @@ -////////////////////////////////////////////////////////////////////// -// iir.cpp: implementation of the CIir class. -// -// This class implements a biquad IIR filter. -// -// Implements a second order IIR filter stage. -// The transfer function of the filter stage implemented is in -// the direct 2 form : -// -// -1 -2 -// B0 + B1 z + B2 z -// H(z) = -------------------- -// -1 -2 -// 1 + A1 z + A2 z -// -// The block diagram used in the implementation is given below: -// -// input w(n) B0 output -// -----> + ----------+-----[>--> + -----> -// | | | -// | +--+--+ | -// | |Delay| | -// | +--+--+ | -// | -A1 |w(n-1) B1 | -// +----<]-----+-------[>--+ -// | | | -// | +--+--+ | -// | |Delay| | -// | +--+--+ | -// | -A2 |w(n-2) B2 | -// +----<]-----+-------[>--+ -// w(n) = in - A1*w(n-1) - A2*w(n-2) -// out = B0*w(n) + B1*w(n-1) + B2*w(n-2) -// w(n-2) = w(n-1) w(n-1) = w(n) -//*========================================================================================= -// The filter design equations came from a paper by Robert Bristow-Johnson -// "Cookbook formulae for audio EQ biquad filter coefficients" -// -// History: -// 2011-02-05 Initial creation MSW -// 2011-03-27 Initial release -// 2013-07-28 Added single/double precision math macros -////////////////////////////////////////////////////////////////////// - -//========================================================================================== -// + + + This Software is released under the "Simplified BSD License" + + + -//Copyright 2010 Moe Wheatley. All rights reserved. -// -//Redistribution and use in source and binary forms, with or without modification, are -//permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other materials -// provided with the distribution. -// -//THIS SOFTWARE IS PROVIDED BY Moe Wheatley ``AS IS'' AND ANY EXPRESS OR IMPLIED -//WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -//FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Moe Wheatley OR -//CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -//CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -//SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -//ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -//NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -//ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -//The views and conclusions contained in the software and documentation are those of the -//authors and should not be interpreted as representing official policies, either expressed -//or implied, of Moe Wheatley. -//========================================================================================== -#include "iir.h" - -///////////////////////////////////////////////////////////////////////////////// -// Construct CIir object -///////////////////////////////////////////////////////////////////////////////// -CIir::CIir() -{ - InitBR( 25000, 1000.0, 100000); -} - -///////////////////////////////////////////////////////////////////////////////// -// Iniitalize IIR variables for Low Pass IIR filter. -// analog prototype == H(s) = 1 / (s^2 + s/Q + 1) -///////////////////////////////////////////////////////////////////////////////// -void CIir::InitLP( TYPEREAL F0Freq, TYPEREAL FilterQ, TYPEREAL SampleRate) -{ - TYPEREAL w0 = K_2PI * F0Freq/SampleRate; //normalized corner frequency - TYPEREAL alpha = MSIN(w0)/(2.0*FilterQ); - TYPEREAL A = 1.0/(1.0 + alpha); //scale everything by 1/A0 for direct form 2 - m_B0 = A*( (1.0 - MCOS(w0))/2.0); - m_B1 = A*( 1.0 - MCOS(w0)); - m_B2 = A*( (1.0 - MCOS(w0))/2.0); - m_A1 = A*( -2.0*MCOS(w0)); - m_A2 = A*( 1.0 - alpha); - - m_w1a = 0.0; - m_w2a = 0.0; - m_w1b = 0.0; - m_w2b = 0.0; -} - - -///////////////////////////////////////////////////////////////////////////////// -// Iniitalize IIR variables for High Pass IIR filter. -// analog prototype == H(s) = s^2 / (s^2 + s/Q + 1) -///////////////////////////////////////////////////////////////////////////////// -void CIir::InitHP( TYPEREAL F0Freq, TYPEREAL FilterQ, TYPEREAL SampleRate) -{ - TYPEREAL w0 = K_2PI * F0Freq/SampleRate; //normalized corner frequency - TYPEREAL alpha = MSIN(w0)/(2.0*FilterQ); - TYPEREAL A = 1.0/(1.0 + alpha); //scale everything by 1/A0 for direct form 2 - m_B0 = A*( (1.0 + MCOS(w0))/2.0); - m_B1 = -A*( 1.0 + MCOS(w0)); - m_B2 = A*( (1.0 + MCOS(w0))/2.0); - m_A1 = A*( -2.0*MCOS(w0)); - m_A2 = A*( 1.0 - alpha); - - m_w1a = 0.0; - m_w2a = 0.0; - m_w1b = 0.0; - m_w2b = 0.0; -} - -///////////////////////////////////////////////////////////////////////////////// -// Iniitalize IIR variables for Band Pass IIR filter. -// analog prototype == H(s) = (s/Q) / (s^2 + s/Q + 1) -///////////////////////////////////////////////////////////////////////////////// -void CIir::InitBP( TYPEREAL F0Freq, TYPEREAL FilterQ, TYPEREAL SampleRate) -{ - TYPEREAL w0 = K_2PI * F0Freq/SampleRate; //normalized corner frequency - TYPEREAL alpha = MSIN(w0)/(2.0*FilterQ); - TYPEREAL A = 1.0/(1.0 + alpha); //scale everything by 1/A0 for direct form 2 - m_B0 = A * alpha; - m_B1 = 0.0; - m_B2 = A * -alpha; - m_A1 = A*( -2.0*MCOS(w0)); - m_A2 = A*( 1.0 - alpha); - - m_w1a = 0.0; - m_w2a = 0.0; - m_w1b = 0.0; - m_w2b = 0.0; -} - -///////////////////////////////////////////////////////////////////////////////// -// Iniitalize IIR variables for Band Reject(Notch) IIR filter. -// analog prototype == H(s) = (s^2 + 1) / (s^2 + s/Q + 1) -///////////////////////////////////////////////////////////////////////////////// -void CIir::InitBR( TYPEREAL F0Freq, TYPEREAL FilterQ, TYPEREAL SampleRate) -{ - TYPEREAL w0 = K_2PI * F0Freq/SampleRate; //normalized corner frequency - TYPEREAL alpha = MSIN(w0)/(2.0*FilterQ); - TYPEREAL A = 1.0/(1.0 + alpha); //scale everything by 1/A0 for direct form 2 - m_B0 = A*1.0; - m_B1 = A*( -2.0*MCOS(w0)); - m_B2 = A*1.0; - m_A1 = A*( -2.0*MCOS(w0)); - m_A2 = A*( 1.0 - alpha); - - m_w1a = 0.0; - m_w2a = 0.0; - m_w1b = 0.0; - m_w2b = 0.0; -} - -///////////////////////////////////////////////////////////////////////////////// -// Process InLength InBuf[] samples and place in OutBuf[] -//REAL version -///////////////////////////////////////////////////////////////////////////////// -void CIir::ProcessFilter(int InLength, TYPEREAL* InBuf, TYPEREAL* OutBuf) -{ - for(int i=0; i + ----------+-----[>--> + -----> +// | | | +// | +--+--+ | +// | |Delay| | +// | +--+--+ | +// | -A1 |w(n-1) B1 | +// +----<]-----+-------[>--+ +// | | | +// | +--+--+ | +// | |Delay| | +// | +--+--+ | +// | -A2 |w(n-2) B2 | +// +----<]-----+-------[>--+ +// w(n) = in - A1*w(n-1) - A2*w(n-2) +// out = B0*w(n) + B1*w(n-1) + B2*w(n-2) +// w(n-2) = w(n-1) w(n-1) = w(n) +//*========================================================================================= +// The filter design equations came from a paper by Robert Bristow-Johnson +// "Cookbook formulae for audio EQ biquad filter coefficients" +// +// History: +// 2011-02-05 Initial creation MSW +// 2011-03-27 Initial release +// 2013-07-28 Added single/double precision math macros +////////////////////////////////////////////////////////////////////// + +//========================================================================================== +// + + + This Software is released under the "Simplified BSD License" + + + +//Copyright 2010 Moe Wheatley. All rights reserved. +// +//Redistribution and use in source and binary forms, with or without modification, are +//permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other materials +// provided with the distribution. +// +//THIS SOFTWARE IS PROVIDED BY Moe Wheatley ``AS IS'' AND ANY EXPRESS OR IMPLIED +//WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +//FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Moe Wheatley OR +//CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +//CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +//SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +//ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +//NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +//ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +//The views and conclusions contained in the software and documentation are those of the +//authors and should not be interpreted as representing official policies, either expressed +//or implied, of Moe Wheatley. +//========================================================================================== +#include "iir.h" + +///////////////////////////////////////////////////////////////////////////////// +// Construct CIir object +///////////////////////////////////////////////////////////////////////////////// +CIir::CIir() +{ + InitBR( 25000, 1000.0, 100000); +} + +///////////////////////////////////////////////////////////////////////////////// +// Iniitalize IIR variables for Low Pass IIR filter. +// analog prototype == H(s) = 1 / (s^2 + s/Q + 1) +///////////////////////////////////////////////////////////////////////////////// +void CIir::InitLP( TYPEREAL F0Freq, TYPEREAL FilterQ, TYPEREAL SampleRate) +{ + TYPEREAL w0 = K_2PI * F0Freq/SampleRate; //normalized corner frequency + TYPEREAL alpha = MSIN(w0)/(2.0*FilterQ); + TYPEREAL A = 1.0/(1.0 + alpha); //scale everything by 1/A0 for direct form 2 + m_B0 = A*( (1.0 - MCOS(w0))/2.0); + m_B1 = A*( 1.0 - MCOS(w0)); + m_B2 = A*( (1.0 - MCOS(w0))/2.0); + m_A1 = A*( -2.0*MCOS(w0)); + m_A2 = A*( 1.0 - alpha); + + m_w1a = 0.0; + m_w2a = 0.0; + m_w1b = 0.0; + m_w2b = 0.0; +} + + +///////////////////////////////////////////////////////////////////////////////// +// Iniitalize IIR variables for High Pass IIR filter. +// analog prototype == H(s) = s^2 / (s^2 + s/Q + 1) +///////////////////////////////////////////////////////////////////////////////// +void CIir::InitHP( TYPEREAL F0Freq, TYPEREAL FilterQ, TYPEREAL SampleRate) +{ + TYPEREAL w0 = K_2PI * F0Freq/SampleRate; //normalized corner frequency + TYPEREAL alpha = MSIN(w0)/(2.0*FilterQ); + TYPEREAL A = 1.0/(1.0 + alpha); //scale everything by 1/A0 for direct form 2 + m_B0 = A*( (1.0 + MCOS(w0))/2.0); + m_B1 = -A*( 1.0 + MCOS(w0)); + m_B2 = A*( (1.0 + MCOS(w0))/2.0); + m_A1 = A*( -2.0*MCOS(w0)); + m_A2 = A*( 1.0 - alpha); + + m_w1a = 0.0; + m_w2a = 0.0; + m_w1b = 0.0; + m_w2b = 0.0; +} + +///////////////////////////////////////////////////////////////////////////////// +// Iniitalize IIR variables for Band Pass IIR filter. +// analog prototype == H(s) = (s/Q) / (s^2 + s/Q + 1) +///////////////////////////////////////////////////////////////////////////////// +void CIir::InitBP( TYPEREAL F0Freq, TYPEREAL FilterQ, TYPEREAL SampleRate) +{ + TYPEREAL w0 = K_2PI * F0Freq/SampleRate; //normalized corner frequency + TYPEREAL alpha = MSIN(w0)/(2.0*FilterQ); + TYPEREAL A = 1.0/(1.0 + alpha); //scale everything by 1/A0 for direct form 2 + m_B0 = A * alpha; + m_B1 = 0.0; + m_B2 = A * -alpha; + m_A1 = A*( -2.0*MCOS(w0)); + m_A2 = A*( 1.0 - alpha); + + m_w1a = 0.0; + m_w2a = 0.0; + m_w1b = 0.0; + m_w2b = 0.0; +} + +///////////////////////////////////////////////////////////////////////////////// +// Iniitalize IIR variables for Band Reject(Notch) IIR filter. +// analog prototype == H(s) = (s^2 + 1) / (s^2 + s/Q + 1) +///////////////////////////////////////////////////////////////////////////////// +void CIir::InitBR( TYPEREAL F0Freq, TYPEREAL FilterQ, TYPEREAL SampleRate) +{ + TYPEREAL w0 = K_2PI * F0Freq/SampleRate; //normalized corner frequency + TYPEREAL alpha = MSIN(w0)/(2.0*FilterQ); + TYPEREAL A = 1.0/(1.0 + alpha); //scale everything by 1/A0 for direct form 2 + m_B0 = A*1.0; + m_B1 = A*( -2.0*MCOS(w0)); + m_B2 = A*1.0; + m_A1 = A*( -2.0*MCOS(w0)); + m_A2 = A*( 1.0 - alpha); + + m_w1a = 0.0; + m_w2a = 0.0; + m_w1b = 0.0; + m_w2b = 0.0; +} + +///////////////////////////////////////////////////////////////////////////////// +// Process InLength InBuf[] samples and place in OutBuf[] +//REAL version +///////////////////////////////////////////////////////////////////////////////// +void CIir::ProcessFilter(int InLength, TYPEREAL* InBuf, TYPEREAL* OutBuf) +{ + for(int i=0; i400000)//need dec by 8 - { - m_pDecBy2C = new CDecimateBy2(HB47TAP_LENGTH, HB47TAP_H); - m_OutRate /= 2.0; - } - if(m_SampleRate>200000)//need dec by 4 - { - m_pDecBy2B = new CDecimateBy2(HB47TAP_LENGTH, HB47TAP_H); - m_OutRate /= 2.0; - } - if(m_SampleRate>100000)//need dec by 2 - { - m_pDecBy2A = new CDecimateBy2(HB47TAP_LENGTH, HB47TAP_H); - m_OutRate /= 2.0; - } - - //set Stereo Pilot phase adjustment values based on sample rate - // compensation function is a straight line approximation with - // form y = Mx + B - m_PilotPhaseAdjust = PHASE_ADJ_M*m_SampleRate + PHASE_ADJ_B; - - m_MonoLPFilter.InitLP(75000, 1.0, m_SampleRate); - - //create filters to create baseband complex data from real fmdemoulator output - m_HilbertFilter.InitConstFir(HILB_LENGTH, HILBLP_H, m_SampleRate); - m_HilbertFilter.GenerateHBFilter(42000); //shift +/-30KHz LP filter by 42KHz to make 12 to 72KHz bandpass - - //Create narrow BP filter around 19KHz pilot tone with Q=500 - m_PilotBPFilter.InitBP(PILOTPLL_FREQ, 500, m_SampleRate); - InitPilotPll(m_SampleRate); - - //create LP filter to roll off audio - m_LPFilter.InitLPFilter(0, 1.0,60.0, 15000.0,1.4*15000.0, m_OutRate); - - //create 19KHz pilot notch filter with Q=5 - m_NotchFilter.InitBR(PILOTPLL_FREQ, 5, m_OutRate); - //create deemphasis filter with 75uSec or 50uSec LP corner - if(USver) - InitDeemphasis(75E-6, m_OutRate); - else - InitDeemphasis(50E-6, m_OutRate); - - m_RdsOutputRate = m_RdsDownConvert.SetDataRate(m_SampleRate, 8000.0); - m_RdsDownConvert.SetFrequency(-RDS_FREQUENCY); //set up to shift 57KHz RDS down to baseband and decimate - - InitRds(m_RdsOutputRate); - m_PilotLocked = false; - m_LastPilotLocked = !m_PilotLocked; - return m_OutRate; -} - -///////////////////////////////////////////////////////////////////////////////// -// Process WFM demod MONO version -// Simple demod without stereo or RDS decoding -// -// InLength == number of complex input samples in complex array pInData -// pInData == pointer to callers complex input array (users input data is overwriten!!) -// pOutData == pointer to callers real(mono audio) output array -// returns number of samples placed in callers output array -///////////////////////////////////////////////////////////////////////////////// -int CWFmDemod::ProcessData(int InLength, TYPECPX* pInData, TYPEREAL* pOutData) -{ - m_MonoLPFilter.ProcessFilter(InLength,pInData, pInData); - - for(int i=0; iDecBy2(InLength, pOutData, pOutData); - if(m_pDecBy2B) - InLength = m_pDecBy2B->DecBy2(InLength, pOutData, pOutData); - if(m_pDecBy2C) - InLength = m_pDecBy2C->DecBy2(InLength, pOutData, pOutData); - - m_LPFilter.ProcessFilter( InLength, pOutData, pOutData); //rolloff audio above 15KHz - ProcessDeemphasisFilter(InLength, pOutData, pOutData); //50 or 75uSec de-emphasis one pole filter - m_NotchFilter.ProcessFilter( InLength, pOutData, pOutData); //notch out 19KHz pilot - m_PilotLocked = false; - return InLength; -} - - -///////////////////////////////////////////////////////////////////////////////// -// Process WFM demod STEREO version -// Process complex I/Q baseband data input by: -// Perform wideband FM demod into a REAL data stream. -// Perform REAL to complex filtering to make easier to shift and process signals -// within the demodulated FM signal. -// IIR Filter around the 19KHz Pilot then Phase Lock a PLL to it. -// If locked, perform stereo demuxing using the PLL signal and a delay line to -// match the raw REAL data stream. -// Shift the 57KHz RDS signal to baseband and decimate its sample rate down. -// PLL the DSB RDS signal and recover the RDS DSB signal. -// Run the RDS signal through a matched filter to recover the biphase data. -// Use a IIR resonator to recover the bit clock and sample the RDS data. -// Call the RDS decoder routine with each new bit to recover the RDS data groups. -// -// InLength == number of complex input samples in complex array pInData -// pInData == pointer to callers complex input array -// pOutData == pointer to callers complex(stereo audio) output array -// returns number of samples placed in callers output array -///////////////////////////////////////////////////////////////////////////////// -int CWFmDemod::ProcessData(int InLength, TYPECPX* pInData, TYPECPX* pOutData) -{ -TYPEREAL LminusR; - for(int i=0; i=0) - { - bit = 1; - m_RdsRaw[i].re = m_RdsLastData; - } - else - { - bit = 0; - m_RdsRaw[i].re = m_RdsLastData; - } - //need to XOR with previous bit to get actual data bit value - ProcessNewRdsBit(bit^m_RdsLastBit); //go process new RDS Bit - m_RdsLastBit = bit; - } - else - { - m_RdsRaw[i].re = 0; - } - - m_RdsLastData = Data; //keep last bit since is differential data - m_RdsLastSyncSlope = Slope; - m_RdsRaw[i].im = Data; - } - - //decimate by 2's down close to final audio rate - if(m_pDecBy2A) - InLength = m_pDecBy2A->DecBy2(InLength, pOutData, pOutData); - if(m_pDecBy2B) - InLength = m_pDecBy2B->DecBy2(InLength, pOutData, pOutData); - if(m_pDecBy2C) - InLength = m_pDecBy2C->DecBy2(InLength, pOutData, pOutData); - - m_LPFilter.ProcessFilter( InLength, pOutData, pOutData); //rolloff audio above 15KHz - ProcessDeemphasisFilter(InLength, pOutData, pOutData); //50 or 75uSec de-emphasis one pole filter - m_NotchFilter.ProcessFilter( InLength, pOutData, pOutData); //notch out 19KHz pilot - return InLength; -} - -///////////////////////////////////////////////////////////////////////////////// -// Iniitalize variables for FM Pilot PLL -///////////////////////////////////////////////////////////////////////////////// -void CWFmDemod::InitPilotPll( TYPEREAL SampleRate ) -{ - m_PilotNcoPhase = 0.0; - m_PilotNcoFreq = -PILOTPLL_FREQ; //freq offset to bring to baseband - - TYPEREAL norm = K_2PI/SampleRate; //to normalize Hz to radians - - //initialize the PLL - m_PilotNcoLLimit = (m_PilotNcoFreq-PILOTPLL_RANGE) * norm; //clamp FM PLL NCO - m_PilotNcoHLimit = (m_PilotNcoFreq+PILOTPLL_RANGE) * norm; - m_PilotPllAlpha = 2.0*PILOTPLL_ZETA*PILOTPLL_BW * norm; - m_PilotPllBeta = (m_PilotPllAlpha * m_PilotPllAlpha)/(4.0*PILOTPLL_ZETA*PILOTPLL_ZETA); - m_PhaseErrorMagAve = 0.0; - m_PhaseErrorMagAlpha = (1.0-MEXP(-1.0/(m_SampleRate*LOCK_TIMECONST)) ); -} - -///////////////////////////////////////////////////////////////////////////////// -// Process IQ wide FM data to lock Pilot PLL -//returns true if Locked. Fills m_PilotPhase[] with locked 19KHz NCO phase data -///////////////////////////////////////////////////////////////////////////////// -bool CWFmDemod::ProcessPilotPll( int InLength, TYPECPX* pInData ) -{ -TYPEREAL Sin; -TYPEREAL Cos; -TYPECPX tmp; - for(int i=0; i m_PilotNcoHLimit) - m_PilotNcoFreq = m_PilotNcoHLimit; - else if(m_PilotNcoFreq < m_PilotNcoLLimit) - m_PilotNcoFreq = m_PilotNcoLLimit; - //update NCO phase with new value - m_PilotNcoPhase += (m_PilotNcoFreq + m_PilotPllAlpha * phzerror); - m_PilotPhase[i] = m_PilotNcoPhase + m_PilotPhaseAdjust; //phase fudge for exact phase delay - //create long average of error magnitude for lock detection - m_PhaseErrorMagAve = (1.0-m_PhaseErrorMagAlpha)*m_PhaseErrorMagAve + m_PhaseErrorMagAlpha*phzerror*phzerror; - } - m_PilotNcoPhase = MFMOD(m_PilotNcoPhase, K_2PI); //keep radian counter bounded - if(m_PhaseErrorMagAve < LOCK_MAG_THRESHOLD) - return true; - else - return false; -} - -///////////////////////////////////////////////////////////////////////////////// -// Get present Stereo lock status and put in pPilotLock. -// Returns true if lock status has changed since last call. -///////////////////////////////////////////////////////////////////////////////// -int CWFmDemod::GetStereoLock(int* pPilotLock) -{ - if(pPilotLock) - *pPilotLock = m_PilotLocked; - if(m_PilotLocked != m_LastPilotLocked) - { - m_LastPilotLocked = m_PilotLocked; - return true; - } - else - return false; -} - -///////////////////////////////////////////////////////////////////////////////// -// Iniitalize IIR variables for De-emphasis IIR filter. -///////////////////////////////////////////////////////////////////////////////// -void CWFmDemod::InitDeemphasis( TYPEREAL Time, TYPEREAL SampleRate) //create De-emphasis LP filter -{ - m_DeemphasisAlpha = (1.0-MEXP(-1.0/(SampleRate*Time)) ); - m_DeemphasisAveRe = 0.0; - m_DeemphasisAveIm = 0.0; -} - -///////////////////////////////////////////////////////////////////////////////// -// Process InLength InBuf[] samples and place in OutBuf[] -//REAL version -///////////////////////////////////////////////////////////////////////////////// -void CWFmDemod::ProcessDeemphasisFilter(int InLength, TYPEREAL* InBuf, TYPEREAL* OutBuf) -{ - for(int i=0; i(SampleRate / RDS_BITRATE); - for(int i= 0; i<=m_MatchCoefLength; i++) - { - TYPEREAL t = (TYPEREAL)i/(SampleRate); - TYPEREAL x = t*RDS_BITRATE; - TYPEREAL x64 = 64.0*x; - m_RdsMatchCoef[i+m_MatchCoefLength] = .75*MCOS(2.0*K_2PI*x)*( (1.0/(1.0/x-x64)) - - (1.0/(9.0/x-x64)) ); - m_RdsMatchCoef[m_MatchCoefLength-i] = -.75*MCOS(2.0*K_2PI*x)*( (1.0/(1.0/x-x64)) - - (1.0/(9.0/x-x64)) ); - } - m_MatchCoefLength *= 2; - //load the matched filter coef into FIR filter - m_RdsMatchedFilter.InitConstFir(m_MatchCoefLength, m_RdsMatchCoef, SampleRate); - //create Hi-Q resonator at the bit rate to recover bit sync position Q==500 - m_RdsBitSyncFilter.InitBP(RDS_BITRATE, 500, SampleRate); - //initialize a bunch of variables pertaining to the rds decoder - m_RdsLastSync = 0.0; - m_RdsLastSyncSlope = 0.0; - m_RdsQHead = 0; - m_RdsQTail = 0; - m_RdsLastBit = 0; - m_CurrentBitPosition = 0; - m_CurrentBlock = BLOCK_A; - m_DecodeState = STATE_BITSYNC; - m_BGroupOffset = 0; - m_LastRdsGroup.BlockA = 0; - m_LastRdsGroup.BlockB = 0; - m_LastRdsGroup.BlockC = 0; - m_LastRdsGroup.BlockD = 0; -} - -///////////////////////////////////////////////////////////////////////////////// -// Process I/Q RDS baseband stream to lock PLL -///////////////////////////////////////////////////////////////////////////////// -void CWFmDemod::ProcessRdsPll( int InLength, TYPECPX* pInData, TYPEREAL* pOutData ) -{ -TYPEREAL Sin; -TYPEREAL Cos; -TYPECPX tmp; - for(int i=0; i m_RdsNcoHLimit) - m_RdsNcoFreq = m_RdsNcoHLimit; - else if(m_RdsNcoFreq < m_RdsNcoLLimit) - m_RdsNcoFreq = m_RdsNcoLLimit; - //update NCO phase with new value - m_RdsNcoPhase += (m_RdsNcoFreq + m_RdsPllAlpha * phzerror); - pOutData[i] = tmp.im; - } - m_RdsNcoPhase = MFMOD(m_RdsNcoPhase, K_2PI); //keep radian counter bounded -} - - - -///////////////////////////////////////////////////////////////////////////////// -// Process one new bit from RDS data stream. -// Manages state machine to find block data bit position, runs chksum and FEC on -// each block, recovers good groups of 4 data blocks and places in data queue -// for further upper level GUI processing depending on the application -///////////////////////////////////////////////////////////////////////////////// -void CWFmDemod::ProcessNewRdsBit(int bit) -{ - m_InBitStream = (m_InBitStream<<1) | bit; //shift in new bit - switch(m_DecodeState) - { - case STATE_BITSYNC: //looking at each bit position till we find a "good" block A - if( 0 == CheckBlock(OFFSET_SYNDROME_BLOCK_A, false) ) - { //got initial good chkword on Block A not using FEC - m_CurrentBitPosition = 0; - m_BGroupOffset = 0; - m_BlockData[BLOCK_A] = m_InBitStream>>NUMBITS_CRC; - m_CurrentBlock = BLOCK_B; - m_DecodeState = STATE_BLOCKSYNC; //next state is looking for blocks B,C, and D in sequence - } - break; - case STATE_BLOCKSYNC: //Looking for 4 blocks in correct sequence to have good probability bit position is good - m_CurrentBitPosition++; - if(m_CurrentBitPosition >= NUMBITS_BLOCK) - { - m_CurrentBitPosition = 0; - if( CheckBlock(BLK_OFFSET_TBL[m_CurrentBlock+m_BGroupOffset], false ) ) - { //bad chkword so go look for bit sync again - m_DecodeState = STATE_BITSYNC; - } - else - { //good chkword so save data and setup for next block - m_BlockData[m_CurrentBlock] = m_InBitStream>>NUMBITS_CRC; //save msg data - //see if is group A or Group B - if( (BLOCK_B == m_CurrentBlock) && (m_BlockData[m_CurrentBlock] & GROUPB_BIT) ) - m_BGroupOffset = 4; - else - m_BGroupOffset = 0; - if(m_CurrentBlock >= BLOCK_D) - { //good chkword on all 4 blocks in correct sequence so are sure of bit position - //Place all group data into data queue - m_RdsGroupQueue[m_RdsQHead].BlockA = m_BlockData[BLOCK_A]; - m_RdsGroupQueue[m_RdsQHead].BlockB = m_BlockData[BLOCK_B]; - m_RdsGroupQueue[m_RdsQHead].BlockC = m_BlockData[BLOCK_C]; - m_RdsGroupQueue[m_RdsQHead++].BlockD = m_BlockData[BLOCK_D]; - if(m_RdsQHead >= RDS_Q_SIZE ) - m_RdsQHead = 0; - m_CurrentBlock = BLOCK_A; - m_BlockErrors = 0; - m_DecodeState = STATE_GROUPDECODE; - } - else - m_CurrentBlock++; - } - } - break; - case STATE_GROUPDECODE: //here after getting a good sequence of blocks - m_CurrentBitPosition++; - if(m_CurrentBitPosition>=NUMBITS_BLOCK) - { - m_CurrentBitPosition = 0; - if( CheckBlock(BLK_OFFSET_TBL[m_CurrentBlock+m_BGroupOffset], USE_FEC ) ) - { - m_BlockErrors++; - if( m_BlockErrors > BLOCK_ERROR_LIMIT ) - { - m_RdsQHead = m_RdsQTail = 0; //clear data queue - m_RdsGroupQueue[m_RdsQHead].BlockA = 0; //stuff all zeros in que to indicate - m_RdsGroupQueue[m_RdsQHead].BlockB = 0; //loss of signal - m_RdsGroupQueue[m_RdsQHead].BlockC = 0; - m_RdsGroupQueue[m_RdsQHead++].BlockD = 0; - m_DecodeState = STATE_BITSYNC; - } - else - { - m_CurrentBlock++; - if(m_CurrentBlock>BLOCK_D) - m_CurrentBlock = BLOCK_A; - if( BLOCK_A != m_CurrentBlock ) //skip remaining blocks of this group if error - m_DecodeState = STATE_GROUPRESYNC; - } - } - else - { //good block so save and get ready for next one - m_BlockData[m_CurrentBlock] = m_InBitStream>>NUMBITS_CRC; //save msg data - //see if is group A or Group B - if( (BLOCK_B == m_CurrentBlock) && (m_BlockData[m_CurrentBlock] & GROUPB_BIT) ) - m_BGroupOffset = 4; - else - m_BGroupOffset = 0; - m_CurrentBlock++; - if(m_CurrentBlock>BLOCK_D) - { - //Place all group data into data queue - m_RdsGroupQueue[m_RdsQHead].BlockA = m_BlockData[BLOCK_A]; - m_RdsGroupQueue[m_RdsQHead].BlockB = m_BlockData[BLOCK_B]; - m_RdsGroupQueue[m_RdsQHead].BlockC = m_BlockData[BLOCK_C]; - m_RdsGroupQueue[m_RdsQHead++].BlockD = m_BlockData[BLOCK_D]; - if(m_RdsQHead >= RDS_Q_SIZE ) - m_RdsQHead = 0; - m_CurrentBlock = BLOCK_A; - m_BlockErrors = 0; - //here with complete good group - } - } - } - break; - case STATE_GROUPRESYNC: //ignor blocks until start of next group - m_CurrentBitPosition++; - if(m_CurrentBitPosition>=NUMBITS_BLOCK) - { - m_CurrentBitPosition = 0; - m_CurrentBlock++; - if(m_CurrentBlock>BLOCK_D) - { - m_CurrentBlock = BLOCK_A; - m_DecodeState = STATE_GROUPDECODE; - } - } - break; - } -} - -///////////////////////////////////////////////////////////////////////////////// -// Check block 'm_InBitStream' with 'BlockOffset' for errors. -// if UseFec is false then no FEC is done else correct up to 5 bits. -// Returns zero if no remaining errors if FEC is specified. -///////////////////////////////////////////////////////////////////////////////// -quint32 CWFmDemod::CheckBlock(quint32 SyndromeOffset, int UseFec) -{ - //First calculate syndrome for current 26 m_InBitStream bits - quint32 testblock = (0x3FFFFFF & m_InBitStream); //isolate bottom 26 bits - //copy top 10 bits of block into 10 syndrome bits since first 10 rows - //of the check matrix is just an identity matrix(diagonal one's) - quint32 syndrome = testblock>>16; - for(int i=0; i>= 1; //advance correctable bit position - } - syndrome &= 0x3FF; //isolate syndrome bits if non-zero then still an error - } - return syndrome; -} - -///////////////////////////////////////////////////////////////////////////////// -// Get next group data from RDS data queue. -// Returns zero if queue is empty or null pointer passed or data has not changed -///////////////////////////////////////////////////////////////////////////////// -bool CWFmDemod::GetNextRdsGroupData(tRDS_GROUPS* pGroupData) -{ - if( (m_RdsQHead == m_RdsQTail) || (NULL == pGroupData) ) - { - return false; - } - pGroupData->BlockA = m_RdsGroupQueue[m_RdsQTail].BlockA; - pGroupData->BlockB = m_RdsGroupQueue[m_RdsQTail].BlockB; - pGroupData->BlockC = m_RdsGroupQueue[m_RdsQTail].BlockC; - pGroupData->BlockD = m_RdsGroupQueue[m_RdsQTail++].BlockD; - if(m_RdsQTail >= RDS_Q_SIZE ) - m_RdsQTail = 0; - if( (m_LastRdsGroup.BlockA != pGroupData->BlockA) || - (m_LastRdsGroup.BlockB != pGroupData->BlockB) || - (m_LastRdsGroup.BlockC != pGroupData->BlockC) || - (m_LastRdsGroup.BlockD != pGroupData->BlockD) ) - { - m_LastRdsGroup = *pGroupData; - return true; - } - else - return false; -} - -///////////////////////////////////////////////////////////////////////////////// -// Less acurate but somewhat faster atan2() function -// |error| < 0.005 -// Useful for plls but not for main FM demod if best audio quality desired. -///////////////////////////////////////////////////////////////////////////////// -inline TYPEREAL CWFmDemod::arctan2(TYPEREAL y, TYPEREAL x) -{ -TYPEREAL angle; - if( x == 0.0 ) - { //avoid divide by zero and just return angle - if( y > 0.0 ) return K_PI2; - if( y == 0.0 ) return 0.0; - return -K_PI2; - } - TYPEREAL z = y/x; - if( MFABS( z ) < 1.0 ) - { - angle = z/(1.0 + 0.2854*z*z); - if( x < 0.0 ) - { - if( y < 0.0 ) - return angle - K_PI; - return angle + K_PI; - } - } - else - { - angle = K_PI2 - z/(z*z + 0.2854); - if( y < 0.0 ) - return angle - K_PI; - } - return angle; -} +// wfmdemod.cpp: implementation of the CWFmDemod class. +// +// This class takes I/Q baseband data and performs +// Wideband FM demodulation +// +// History: +// 2011-09-17 Initial creation MSW +// 2011-09-17 Initial release +// 2013-07-28 Added single/double precision math macros +// 2014-09-22 Added some test code to output to a wav file +// 2016-01-10 removed x86 assembly code +////////////////////////////////////////////////////////////////////// + +//========================================================================================== +// + + + This Software is released under the "Simplified BSD License" + + + +//Copyright 2011 Moe Wheatley. All rights reserved. +// +//Redistribution and use in source and binary forms, with or without modification, are +//permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other materials +// provided with the distribution. +// +//THIS SOFTWARE IS PROVIDED BY Moe Wheatley ``AS IS'' AND ANY EXPRESS OR IMPLIED +//WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +//FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Moe Wheatley OR +//CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +//CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +//SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +//ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +//NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +//ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +//The views and conclusions contained in the software and documentation are those of the +//authors and should not be interpreted as representing official policies, either expressed +//or implied, of Moe Wheatley. +//========================================================================================== +#include "wfmdemod.h" +#include "datatypes.h" +#include "filtercoef.h" + +#define FMDEMOD_GAIN 8000.0 + +#define PILOTPLL_RANGE 20.0 //maximum deviation limit of PLL +#define PILOTPLL_BW 10.0 //natural frequency ~loop bandwidth +#define PILOTPLL_ZETA .707 //PLL Loop damping factor +#define PILOTPLL_FREQ 19000.0 //Centerfreq +#define LOCK_TIMECONST .5 //Lock filter time in seconds +#define LOCK_MAG_THRESHOLD 0.1 //Lock error magnitude threshold + +#define PHASE_ADJ_M -7.267e-6 //fudge factor slope to compensate for PLL delay +#define PHASE_ADJ_B 3.677 //fudge factor intercept to compensate for PLL delay + +//bunch of RDS constants +#define USE_FEC 1 //set to zero to disable FEC correction + +#define RDS_FREQUENCY 57000.0 +#define RDS_BITRATE (RDS_FREQUENCY/48.0) //1187.5 bps bitrate +#define RDSPLL_RANGE 12.0 //maximum deviation limit of PLL +#define RDSPLL_BW 1.0 //natural frequency ~loop bandwidth +#define RDSPLL_ZETA .707 //PLL Loop damping factor + +//RDS decoder states +#define STATE_BITSYNC 0 //looking for initial bit position in Block 1 +#define STATE_BLOCKSYNC 1 //looking for initial correct block order +#define STATE_GROUPDECODE 2 //decode groups after achieving bit and block sync +#define STATE_GROUPRESYNC 3 //waiting for beginning of new group after getting a block error + +#define BLOCK_ERROR_LIMIT 5 //number of bad blocks before trying to resync at the bit level + +#define HILB_LENGTH 61 +const TYPEREAL HILBLP_H[HILB_LENGTH] = +{ //LowPass filter prototype that is shifted and "hilbertized" to get 90 deg phase shift + //and convert to baseband complex domain. + //kaiser-Bessel alpha 1.4 cutoff 30Khz at sample rate of 250KHz + -0.000389631665953405,0.000115430826670992,0.000945331102222503,0.001582460677684605, + 0.001370803713784687,-0.000000000000000002,-0.002077413537668161,-0.003656132107176520, + -0.003372610825000167,-0.000649815020884706, 0.003583263233560064, 0.006997162933343487, + 0.006990985399916562, 0.002383133886438500,-0.005324501734543406,-0.012092135317628615, + -0.013212201698221963,-0.006168904735839018, 0.007082277142635906, 0.020017841466263672, + 0.024271835962039127, 0.014255112728911837,-0.008597071392140753,-0.034478282954624850, + -0.048147195828726633,-0.035409729589347565, 0.009623663461671806, 0.080084441681677138, + 0.157278883310078170, 0.217148915611638180, 0.239688166538436750, 0.217148915611638180, + 0.157278883310078170, 0.080084441681677138, 0.009623663461671806,-0.035409729589347565, + -0.048147195828726633,-0.034478282954624850,-0.008597071392140753, 0.014255112728911837, + 0.024271835962039127, 0.020017841466263672, 0.007082277142635906,-0.006168904735839018, + -0.013212201698221963,-0.012092135317628615,-0.005324501734543406, 0.002383133886438500, + 0.006990985399916562, 0.006997162933343487, 0.003583263233560064,-0.000649815020884706, + -0.003372610825000167,-0.003656132107176520,-0.002077413537668161,-0.000000000000000002, + 0.001370803713784687, 0.001582460677684605, 0.000945331102222503, 0.000115430826670992, + -0.000389631665953405 +}; + +#if 0 +const TYPEREAL HILBLP_H[HILB_LENGTH] = { //test wideband hilbert + 0.000639403635f, + -0.000876414761f, + 0.000763344477f, + -0.000136153196f, + -0.000959189633f, + 0.002196797759f, + -0.003022642082f, + 0.002843677701f, + -0.001305921508f, + -0.001445249432f, + 0.004652418499f, + -0.007079194912f, + 0.007402035698f, + -0.004761371611f, + -0.000723462240f, + 0.007705168551f, + -0.013799667280f, + 0.016252251992f, + -0.012930808533f, + 0.003338459172f, + 0.010731465462f, + -0.025212726547f, + 0.034579859350f, + -0.033317627946f, + 0.017655163815f, + 0.012969531446f, + -0.055222198103f, + 0.102266070453f, + -0.145217247362f, + 0.175301692976f, + 0.813425068326f, + 0.175301692976f, + -0.145217247362f, + 0.102266070453f, + -0.055222198103f, + 0.012969531446f, + 0.017655163815f, + -0.033317627946f, + 0.034579859350f, + -0.025212726547f, + 0.010731465462f, + 0.003338459172f, + -0.012930808533f, + 0.016252251992f, + -0.013799667280f, + 0.007705168551f, + -0.000723462240f, + -0.004761371611f, + 0.007402035698f, + -0.007079194912f, + 0.004652418499f, + -0.001445249432f, + -0.001305921508f, + 0.002843677701f, + -0.003022642082f, + 0.002196797759f, + -0.000959189633f, + -0.000136153196f, + 0.000763344477f, + -0.000876414761f, + 0.000639403635f +}; +#endif + +///////////////////////////////////////////////////////////////////////////////// +// Construct/destruct WFM demod object +///////////////////////////////////////////////////////////////////////////////// +CWFmDemod::CWFmDemod(TYPEREAL samplerate) : m_SampleRate(samplerate) +{ + m_pDecBy2A = NULL; + m_pDecBy2B = NULL; + m_pDecBy2C = NULL; + m_PilotPhaseAdjust = 0.0; + SetSampleRate(samplerate, true); + m_InBitStream = 0; + m_CurrentBitPosition = 0; + m_CurrentBlock = BLOCK_A; + m_DecodeState = STATE_BITSYNC; + m_BGroupOffset = 0; + m_PilotLocked = false; + m_LastPilotLocked = !m_PilotLocked; + m_BlockErrors = 0; +} + +CWFmDemod::~CWFmDemod() +{ //destroy resources + if(m_pDecBy2A) + delete m_pDecBy2A; + if(m_pDecBy2B) + delete m_pDecBy2B; + if(m_pDecBy2C) + delete m_pDecBy2C; +} + +///////////////////////////////////////////////////////////////////////////////// +// Sets demodulator parameters based on input sample rate +// returns the audio sample rate that is produced +// Input sample rate should be in the range 200 to 400Ksps +// The output rate will be between 50KHz and 100KHz +///////////////////////////////////////////////////////////////////////////////// +TYPEREAL CWFmDemod::SetSampleRate(TYPEREAL samplerate, bool USver) +{ + //delete any resources that may still exist + if(m_pDecBy2A) + delete m_pDecBy2A; + if(m_pDecBy2B) + delete m_pDecBy2B; + if(m_pDecBy2C) + delete m_pDecBy2C; + m_pDecBy2A = NULL; + m_pDecBy2B = NULL; + m_pDecBy2C = NULL; + + m_OutRate = m_SampleRate = samplerate; + //Determine post demod decimation rate based on input sample rate range + // try to get down to close to 50khz + if(m_SampleRate>400000)//need dec by 8 + { + m_pDecBy2C = new CDecimateBy2(HB47TAP_LENGTH, HB47TAP_H); + m_OutRate /= 2.0; + } + if(m_SampleRate>200000)//need dec by 4 + { + m_pDecBy2B = new CDecimateBy2(HB47TAP_LENGTH, HB47TAP_H); + m_OutRate /= 2.0; + } + if(m_SampleRate>100000)//need dec by 2 + { + m_pDecBy2A = new CDecimateBy2(HB47TAP_LENGTH, HB47TAP_H); + m_OutRate /= 2.0; + } + + //set Stereo Pilot phase adjustment values based on sample rate + // compensation function is a straight line approximation with + // form y = Mx + B + m_PilotPhaseAdjust = PHASE_ADJ_M*m_SampleRate + PHASE_ADJ_B; + + m_MonoLPFilter.InitLP(75000, 1.0, m_SampleRate); + + //create filters to create baseband complex data from real fmdemoulator output + m_HilbertFilter.InitConstFir(HILB_LENGTH, HILBLP_H, m_SampleRate); + m_HilbertFilter.GenerateHBFilter(42000); //shift +/-30KHz LP filter by 42KHz to make 12 to 72KHz bandpass + + //Create narrow BP filter around 19KHz pilot tone with Q=500 + m_PilotBPFilter.InitBP(PILOTPLL_FREQ, 500, m_SampleRate); + InitPilotPll(m_SampleRate); + + //create LP filter to roll off audio + m_LPFilter.InitLPFilter(0, 1.0,60.0, 15000.0,1.4*15000.0, m_OutRate); + + //create 19KHz pilot notch filter with Q=5 + m_NotchFilter.InitBR(PILOTPLL_FREQ, 5, m_OutRate); + //create deemphasis filter with 75uSec or 50uSec LP corner + if(USver) + InitDeemphasis(75E-6, m_OutRate); + else + InitDeemphasis(50E-6, m_OutRate); + + m_RdsOutputRate = m_RdsDownConvert.SetDataRate(m_SampleRate, 8000.0); + m_RdsDownConvert.SetFrequency(-RDS_FREQUENCY); //set up to shift 57KHz RDS down to baseband and decimate + + InitRds(m_RdsOutputRate); + m_PilotLocked = false; + m_LastPilotLocked = !m_PilotLocked; + return m_OutRate; +} + +///////////////////////////////////////////////////////////////////////////////// +// Process WFM demod MONO version +// Simple demod without stereo or RDS decoding +// +// InLength == number of complex input samples in complex array pInData +// pInData == pointer to callers complex input array (users input data is overwriten!!) +// pOutData == pointer to callers real(mono audio) output array +// returns number of samples placed in callers output array +///////////////////////////////////////////////////////////////////////////////// +int CWFmDemod::ProcessData(int InLength, TYPECPX* pInData, TYPEREAL* pOutData) +{ + m_MonoLPFilter.ProcessFilter(InLength,pInData, pInData); + + for(int i=0; iDecBy2(InLength, pOutData, pOutData); + if(m_pDecBy2B) + InLength = m_pDecBy2B->DecBy2(InLength, pOutData, pOutData); + if(m_pDecBy2C) + InLength = m_pDecBy2C->DecBy2(InLength, pOutData, pOutData); + + m_LPFilter.ProcessFilter( InLength, pOutData, pOutData); //rolloff audio above 15KHz + ProcessDeemphasisFilter(InLength, pOutData, pOutData); //50 or 75uSec de-emphasis one pole filter + m_NotchFilter.ProcessFilter( InLength, pOutData, pOutData); //notch out 19KHz pilot + m_PilotLocked = false; + return InLength; +} + + +///////////////////////////////////////////////////////////////////////////////// +// Process WFM demod STEREO version +// Process complex I/Q baseband data input by: +// Perform wideband FM demod into a REAL data stream. +// Perform REAL to complex filtering to make easier to shift and process signals +// within the demodulated FM signal. +// IIR Filter around the 19KHz Pilot then Phase Lock a PLL to it. +// If locked, perform stereo demuxing using the PLL signal and a delay line to +// match the raw REAL data stream. +// Shift the 57KHz RDS signal to baseband and decimate its sample rate down. +// PLL the DSB RDS signal and recover the RDS DSB signal. +// Run the RDS signal through a matched filter to recover the biphase data. +// Use a IIR resonator to recover the bit clock and sample the RDS data. +// Call the RDS decoder routine with each new bit to recover the RDS data groups. +// +// InLength == number of complex input samples in complex array pInData +// pInData == pointer to callers complex input array +// pOutData == pointer to callers complex(stereo audio) output array +// returns number of samples placed in callers output array +///////////////////////////////////////////////////////////////////////////////// +int CWFmDemod::ProcessData(int InLength, TYPECPX* pInData, TYPECPX* pOutData) +{ +TYPEREAL LminusR; + for(int i=0; i=0) + { + bit = 1; + m_RdsRaw[i].re = m_RdsLastData; + } + else + { + bit = 0; + m_RdsRaw[i].re = m_RdsLastData; + } + //need to XOR with previous bit to get actual data bit value + ProcessNewRdsBit(bit^m_RdsLastBit); //go process new RDS Bit + m_RdsLastBit = bit; + } + else + { + m_RdsRaw[i].re = 0; + } + + m_RdsLastData = Data; //keep last bit since is differential data + m_RdsLastSyncSlope = Slope; + m_RdsRaw[i].im = Data; + } + + //decimate by 2's down close to final audio rate + if(m_pDecBy2A) + InLength = m_pDecBy2A->DecBy2(InLength, pOutData, pOutData); + if(m_pDecBy2B) + InLength = m_pDecBy2B->DecBy2(InLength, pOutData, pOutData); + if(m_pDecBy2C) + InLength = m_pDecBy2C->DecBy2(InLength, pOutData, pOutData); + + m_LPFilter.ProcessFilter( InLength, pOutData, pOutData); //rolloff audio above 15KHz + ProcessDeemphasisFilter(InLength, pOutData, pOutData); //50 or 75uSec de-emphasis one pole filter + m_NotchFilter.ProcessFilter( InLength, pOutData, pOutData); //notch out 19KHz pilot + return InLength; +} + +///////////////////////////////////////////////////////////////////////////////// +// Iniitalize variables for FM Pilot PLL +///////////////////////////////////////////////////////////////////////////////// +void CWFmDemod::InitPilotPll( TYPEREAL SampleRate ) +{ + m_PilotNcoPhase = 0.0; + m_PilotNcoFreq = -PILOTPLL_FREQ; //freq offset to bring to baseband + + TYPEREAL norm = K_2PI/SampleRate; //to normalize Hz to radians + + //initialize the PLL + m_PilotNcoLLimit = (m_PilotNcoFreq-PILOTPLL_RANGE) * norm; //clamp FM PLL NCO + m_PilotNcoHLimit = (m_PilotNcoFreq+PILOTPLL_RANGE) * norm; + m_PilotPllAlpha = 2.0*PILOTPLL_ZETA*PILOTPLL_BW * norm; + m_PilotPllBeta = (m_PilotPllAlpha * m_PilotPllAlpha)/(4.0*PILOTPLL_ZETA*PILOTPLL_ZETA); + m_PhaseErrorMagAve = 0.0; + m_PhaseErrorMagAlpha = (1.0-MEXP(-1.0/(m_SampleRate*LOCK_TIMECONST)) ); +} + +///////////////////////////////////////////////////////////////////////////////// +// Process IQ wide FM data to lock Pilot PLL +//returns true if Locked. Fills m_PilotPhase[] with locked 19KHz NCO phase data +///////////////////////////////////////////////////////////////////////////////// +bool CWFmDemod::ProcessPilotPll( int InLength, TYPECPX* pInData ) +{ +TYPEREAL Sin; +TYPEREAL Cos; +TYPECPX tmp; + for(int i=0; i m_PilotNcoHLimit) + m_PilotNcoFreq = m_PilotNcoHLimit; + else if(m_PilotNcoFreq < m_PilotNcoLLimit) + m_PilotNcoFreq = m_PilotNcoLLimit; + //update NCO phase with new value + m_PilotNcoPhase += (m_PilotNcoFreq + m_PilotPllAlpha * phzerror); + m_PilotPhase[i] = m_PilotNcoPhase + m_PilotPhaseAdjust; //phase fudge for exact phase delay + //create long average of error magnitude for lock detection + m_PhaseErrorMagAve = (1.0-m_PhaseErrorMagAlpha)*m_PhaseErrorMagAve + m_PhaseErrorMagAlpha*phzerror*phzerror; + } + m_PilotNcoPhase = MFMOD(m_PilotNcoPhase, K_2PI); //keep radian counter bounded + if(m_PhaseErrorMagAve < LOCK_MAG_THRESHOLD) + return true; + else + return false; +} + +///////////////////////////////////////////////////////////////////////////////// +// Get present Stereo lock status and put in pPilotLock. +// Returns true if lock status has changed since last call. +///////////////////////////////////////////////////////////////////////////////// +int CWFmDemod::GetStereoLock(int* pPilotLock) +{ + if(pPilotLock) + *pPilotLock = m_PilotLocked; + if(m_PilotLocked != m_LastPilotLocked) + { + m_LastPilotLocked = m_PilotLocked; + return true; + } + else + return false; +} + +///////////////////////////////////////////////////////////////////////////////// +// Iniitalize IIR variables for De-emphasis IIR filter. +///////////////////////////////////////////////////////////////////////////////// +void CWFmDemod::InitDeemphasis( TYPEREAL Time, TYPEREAL SampleRate) //create De-emphasis LP filter +{ + m_DeemphasisAlpha = (1.0-MEXP(-1.0/(SampleRate*Time)) ); + m_DeemphasisAveRe = 0.0; + m_DeemphasisAveIm = 0.0; +} + +///////////////////////////////////////////////////////////////////////////////// +// Process InLength InBuf[] samples and place in OutBuf[] +//REAL version +///////////////////////////////////////////////////////////////////////////////// +void CWFmDemod::ProcessDeemphasisFilter(int InLength, TYPEREAL* InBuf, TYPEREAL* OutBuf) +{ + for(int i=0; i(SampleRate / RDS_BITRATE); + for(int i= 0; i<=m_MatchCoefLength; i++) + { + TYPEREAL t = (TYPEREAL)i/(SampleRate); + TYPEREAL x = t*RDS_BITRATE; + TYPEREAL x64 = 64.0*x; + m_RdsMatchCoef[i+m_MatchCoefLength] = .75*MCOS(2.0*K_2PI*x)*( (1.0/(1.0/x-x64)) - + (1.0/(9.0/x-x64)) ); + m_RdsMatchCoef[m_MatchCoefLength-i] = -.75*MCOS(2.0*K_2PI*x)*( (1.0/(1.0/x-x64)) - + (1.0/(9.0/x-x64)) ); + } + m_MatchCoefLength *= 2; + //load the matched filter coef into FIR filter + m_RdsMatchedFilter.InitConstFir(m_MatchCoefLength, m_RdsMatchCoef, SampleRate); + //create Hi-Q resonator at the bit rate to recover bit sync position Q==500 + m_RdsBitSyncFilter.InitBP(RDS_BITRATE, 500, SampleRate); + //initialize a bunch of variables pertaining to the rds decoder + m_RdsLastSync = 0.0; + m_RdsLastSyncSlope = 0.0; + m_RdsQHead = 0; + m_RdsQTail = 0; + m_RdsLastBit = 0; + m_CurrentBitPosition = 0; + m_CurrentBlock = BLOCK_A; + m_DecodeState = STATE_BITSYNC; + m_BGroupOffset = 0; + m_LastRdsGroup.BlockA = 0; + m_LastRdsGroup.BlockB = 0; + m_LastRdsGroup.BlockC = 0; + m_LastRdsGroup.BlockD = 0; +} + +///////////////////////////////////////////////////////////////////////////////// +// Process I/Q RDS baseband stream to lock PLL +///////////////////////////////////////////////////////////////////////////////// +void CWFmDemod::ProcessRdsPll( int InLength, TYPECPX* pInData, TYPEREAL* pOutData ) +{ +TYPEREAL Sin; +TYPEREAL Cos; +TYPECPX tmp; + for(int i=0; i m_RdsNcoHLimit) + m_RdsNcoFreq = m_RdsNcoHLimit; + else if(m_RdsNcoFreq < m_RdsNcoLLimit) + m_RdsNcoFreq = m_RdsNcoLLimit; + //update NCO phase with new value + m_RdsNcoPhase += (m_RdsNcoFreq + m_RdsPllAlpha * phzerror); + pOutData[i] = tmp.im; + } + m_RdsNcoPhase = MFMOD(m_RdsNcoPhase, K_2PI); //keep radian counter bounded +} + + + +///////////////////////////////////////////////////////////////////////////////// +// Process one new bit from RDS data stream. +// Manages state machine to find block data bit position, runs chksum and FEC on +// each block, recovers good groups of 4 data blocks and places in data queue +// for further upper level GUI processing depending on the application +///////////////////////////////////////////////////////////////////////////////// +void CWFmDemod::ProcessNewRdsBit(int bit) +{ + m_InBitStream = (m_InBitStream<<1) | bit; //shift in new bit + switch(m_DecodeState) + { + case STATE_BITSYNC: //looking at each bit position till we find a "good" block A + if( 0 == CheckBlock(OFFSET_SYNDROME_BLOCK_A, false) ) + { //got initial good chkword on Block A not using FEC + m_CurrentBitPosition = 0; + m_BGroupOffset = 0; + m_BlockData[BLOCK_A] = m_InBitStream>>NUMBITS_CRC; + m_CurrentBlock = BLOCK_B; + m_DecodeState = STATE_BLOCKSYNC; //next state is looking for blocks B,C, and D in sequence + } + break; + case STATE_BLOCKSYNC: //Looking for 4 blocks in correct sequence to have good probability bit position is good + m_CurrentBitPosition++; + if(m_CurrentBitPosition >= NUMBITS_BLOCK) + { + m_CurrentBitPosition = 0; + if( CheckBlock(BLK_OFFSET_TBL[m_CurrentBlock+m_BGroupOffset], false ) ) + { //bad chkword so go look for bit sync again + m_DecodeState = STATE_BITSYNC; + } + else + { //good chkword so save data and setup for next block + m_BlockData[m_CurrentBlock] = m_InBitStream>>NUMBITS_CRC; //save msg data + //see if is group A or Group B + if( (BLOCK_B == m_CurrentBlock) && (m_BlockData[m_CurrentBlock] & GROUPB_BIT) ) + m_BGroupOffset = 4; + else + m_BGroupOffset = 0; + if(m_CurrentBlock >= BLOCK_D) + { //good chkword on all 4 blocks in correct sequence so are sure of bit position + //Place all group data into data queue + m_RdsGroupQueue[m_RdsQHead].BlockA = m_BlockData[BLOCK_A]; + m_RdsGroupQueue[m_RdsQHead].BlockB = m_BlockData[BLOCK_B]; + m_RdsGroupQueue[m_RdsQHead].BlockC = m_BlockData[BLOCK_C]; + m_RdsGroupQueue[m_RdsQHead++].BlockD = m_BlockData[BLOCK_D]; + if(m_RdsQHead >= RDS_Q_SIZE ) + m_RdsQHead = 0; + m_CurrentBlock = BLOCK_A; + m_BlockErrors = 0; + m_DecodeState = STATE_GROUPDECODE; + } + else + m_CurrentBlock++; + } + } + break; + case STATE_GROUPDECODE: //here after getting a good sequence of blocks + m_CurrentBitPosition++; + if(m_CurrentBitPosition>=NUMBITS_BLOCK) + { + m_CurrentBitPosition = 0; + if( CheckBlock(BLK_OFFSET_TBL[m_CurrentBlock+m_BGroupOffset], USE_FEC ) ) + { + m_BlockErrors++; + if( m_BlockErrors > BLOCK_ERROR_LIMIT ) + { + m_RdsQHead = m_RdsQTail = 0; //clear data queue + m_RdsGroupQueue[m_RdsQHead].BlockA = 0; //stuff all zeros in que to indicate + m_RdsGroupQueue[m_RdsQHead].BlockB = 0; //loss of signal + m_RdsGroupQueue[m_RdsQHead].BlockC = 0; + m_RdsGroupQueue[m_RdsQHead++].BlockD = 0; + m_DecodeState = STATE_BITSYNC; + } + else + { + m_CurrentBlock++; + if(m_CurrentBlock>BLOCK_D) + m_CurrentBlock = BLOCK_A; + if( BLOCK_A != m_CurrentBlock ) //skip remaining blocks of this group if error + m_DecodeState = STATE_GROUPRESYNC; + } + } + else + { //good block so save and get ready for next one + m_BlockData[m_CurrentBlock] = m_InBitStream>>NUMBITS_CRC; //save msg data + //see if is group A or Group B + if( (BLOCK_B == m_CurrentBlock) && (m_BlockData[m_CurrentBlock] & GROUPB_BIT) ) + m_BGroupOffset = 4; + else + m_BGroupOffset = 0; + m_CurrentBlock++; + if(m_CurrentBlock>BLOCK_D) + { + //Place all group data into data queue + m_RdsGroupQueue[m_RdsQHead].BlockA = m_BlockData[BLOCK_A]; + m_RdsGroupQueue[m_RdsQHead].BlockB = m_BlockData[BLOCK_B]; + m_RdsGroupQueue[m_RdsQHead].BlockC = m_BlockData[BLOCK_C]; + m_RdsGroupQueue[m_RdsQHead++].BlockD = m_BlockData[BLOCK_D]; + if(m_RdsQHead >= RDS_Q_SIZE ) + m_RdsQHead = 0; + m_CurrentBlock = BLOCK_A; + m_BlockErrors = 0; + //here with complete good group + } + } + } + break; + case STATE_GROUPRESYNC: //ignor blocks until start of next group + m_CurrentBitPosition++; + if(m_CurrentBitPosition>=NUMBITS_BLOCK) + { + m_CurrentBitPosition = 0; + m_CurrentBlock++; + if(m_CurrentBlock>BLOCK_D) + { + m_CurrentBlock = BLOCK_A; + m_DecodeState = STATE_GROUPDECODE; + } + } + break; + } +} + +///////////////////////////////////////////////////////////////////////////////// +// Check block 'm_InBitStream' with 'BlockOffset' for errors. +// if UseFec is false then no FEC is done else correct up to 5 bits. +// Returns zero if no remaining errors if FEC is specified. +///////////////////////////////////////////////////////////////////////////////// +quint32 CWFmDemod::CheckBlock(quint32 SyndromeOffset, int UseFec) +{ + //First calculate syndrome for current 26 m_InBitStream bits + quint32 testblock = (0x3FFFFFF & m_InBitStream); //isolate bottom 26 bits + //copy top 10 bits of block into 10 syndrome bits since first 10 rows + //of the check matrix is just an identity matrix(diagonal one's) + quint32 syndrome = testblock>>16; + for(int i=0; i>= 1; //advance correctable bit position + } + syndrome &= 0x3FF; //isolate syndrome bits if non-zero then still an error + } + return syndrome; +} + +///////////////////////////////////////////////////////////////////////////////// +// Get next group data from RDS data queue. +// Returns zero if queue is empty or null pointer passed or data has not changed +///////////////////////////////////////////////////////////////////////////////// +bool CWFmDemod::GetNextRdsGroupData(tRDS_GROUPS* pGroupData) +{ + if( (m_RdsQHead == m_RdsQTail) || (NULL == pGroupData) ) + { + return false; + } + pGroupData->BlockA = m_RdsGroupQueue[m_RdsQTail].BlockA; + pGroupData->BlockB = m_RdsGroupQueue[m_RdsQTail].BlockB; + pGroupData->BlockC = m_RdsGroupQueue[m_RdsQTail].BlockC; + pGroupData->BlockD = m_RdsGroupQueue[m_RdsQTail++].BlockD; + if(m_RdsQTail >= RDS_Q_SIZE ) + m_RdsQTail = 0; + if( (m_LastRdsGroup.BlockA != pGroupData->BlockA) || + (m_LastRdsGroup.BlockB != pGroupData->BlockB) || + (m_LastRdsGroup.BlockC != pGroupData->BlockC) || + (m_LastRdsGroup.BlockD != pGroupData->BlockD) ) + { + m_LastRdsGroup = *pGroupData; + return true; + } + else + return false; +} + +///////////////////////////////////////////////////////////////////////////////// +// Less acurate but somewhat faster atan2() function +// |error| < 0.005 +// Useful for plls but not for main FM demod if best audio quality desired. +///////////////////////////////////////////////////////////////////////////////// +inline TYPEREAL CWFmDemod::arctan2(TYPEREAL y, TYPEREAL x) +{ +TYPEREAL angle; + if( x == 0.0 ) + { //avoid divide by zero and just return angle + if( y > 0.0 ) return K_PI2; + if( y == 0.0 ) return 0.0; + return -K_PI2; + } + TYPEREAL z = y/x; + if( MFABS( z ) < 1.0 ) + { + angle = z/(1.0 + 0.2854*z*z); + if( x < 0.0 ) + { + if( y < 0.0 ) + return angle - K_PI; + return angle + K_PI; + } + } + else + { + angle = K_PI2 - z/(z*z + 0.2854); + if( y < 0.0 ) + return angle - K_PI; + } + return angle; +} diff --git a/src/fmdsp/wfmdemod.h b/src/fmdsp/wfmdemod.h index 9ddbd86..be5b6e0 100644 --- a/src/fmdsp/wfmdemod.h +++ b/src/fmdsp/wfmdemod.h @@ -1,145 +1,145 @@ -////////////////////////////////////////////////////////////////////// -// wfmdemod.h: interface for the CWFmDemod class. -// -// History: -// 2011-07-24 Initial creation MSW -// 2011-08-05 Initial release -///////////////////////////////////////////////////////////////////// -//========================================================================================== -// + + + This Software is released under the "Simplified BSD License" + + + -//Copyright 2010 Moe Wheatley. All rights reserved. -// -//Redistribution and use in source and binary forms, with or without modification, are -//permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other materials -// provided with the distribution. -// -//THIS SOFTWARE IS PROVIDED BY Moe Wheatley ``AS IS'' AND ANY EXPRESS OR IMPLIED -//WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -//FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Moe Wheatley OR -//CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -//CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -//SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -//ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -//NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -//ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -//The views and conclusions contained in the software and documentation are those of the -//authors and should not be interpreted as representing official policies, either expressed -//or implied, of Moe Wheatley. -//============================================================================= -#ifndef WFMDEMOD_H -#define WFMDEMOD_H -#include "datatypes.h" -#include "fir.h" -#include "iir.h" -#include "downconvert.h" -#include "rbdsconstants.h" - - -#define PHZBUF_SIZE 16384 - -#define RDS_Q_SIZE 100 - -class CWFmDemod -{ -public: - CWFmDemod(TYPEREAL samplerate); - virtual ~CWFmDemod(); - - TYPEREAL SetSampleRate(TYPEREAL samplerate, bool USver); - //overloaded functions for mono and stereo - int ProcessData(int InLength, TYPECPX* pInData, TYPECPX* pOutData); - int ProcessData(int InLength, TYPECPX* pInData, TYPEREAL* pOutData); - TYPEREAL GetDemodRate(){return m_OutRate;} - - bool GetNextRdsGroupData(tRDS_GROUPS* pGroupData); - int GetStereoLock(int* pPilotLock); - -private: - void InitDeemphasis( TYPEREAL Time, TYPEREAL SampleRate); //create De-emphasis LP filter - void ProcessDeemphasisFilter(int InLength, TYPEREAL* InBuf, TYPEREAL* OutBuf); - void ProcessDeemphasisFilter(int InLength, TYPECPX* InBuf, TYPECPX* OutBuf); - void InitPilotPll( TYPEREAL SampleRate ); - bool ProcessPilotPll( int InLength, TYPECPX* pInData ); - void InitRds( TYPEREAL SampleRate ); - void ProcessRdsPll( int InLength, TYPECPX* pInData, TYPEREAL* pOutData ); - inline TYPEREAL arctan2(TYPEREAL y, TYPEREAL x); - - void ProcessNewRdsBit(int bit); - quint32 CheckBlock(quint32 BlockOffset, int UseFec); - - TYPEREAL m_SampleRate; - TYPEREAL m_OutRate; - TYPEREAL m_RawFm[PHZBUF_SIZE]; - TYPECPX m_CpxRawFm[PHZBUF_SIZE]; - CDecimateBy2* m_pDecBy2A; - CDecimateBy2* m_pDecBy2B; - CDecimateBy2* m_pDecBy2C; - - TYPECPX m_D0; //complex delay line variables - TYPECPX m_D1; - - TYPEREAL m_DeemphasisAveRe; - TYPEREAL m_DeemphasisAveIm; - TYPEREAL m_DeemphasisAlpha; - - CIir m_MonoLPFilter; - CFir m_LPFilter; - CIir m_NotchFilter; - CIir m_PilotBPFilter; - CFir m_HilbertFilter; - - int m_PilotLocked; //variables for Pilot PLL - int m_LastPilotLocked; - TYPEREAL m_PilotNcoPhase; - TYPEREAL m_PilotNcoFreq; - TYPEREAL m_PilotNcoLLimit; - TYPEREAL m_PilotNcoHLimit; - TYPEREAL m_PilotPllAlpha; - TYPEREAL m_PilotPllBeta; - TYPEREAL m_PhaseErrorMagAve; - TYPEREAL m_PhaseErrorMagAlpha; - TYPEREAL m_PilotPhase[PHZBUF_SIZE]; - TYPEREAL m_PilotPhaseAdjust; - - TYPEREAL m_RdsNcoPhase; //variables for RDS PLL - TYPEREAL m_RdsNcoFreq; - TYPEREAL m_RdsNcoLLimit; - TYPEREAL m_RdsNcoHLimit; - TYPEREAL m_RdsPllAlpha; - TYPEREAL m_RdsPllBeta; - - TYPECPX m_RdsRaw[PHZBUF_SIZE]; //variables for RDS processing - TYPEREAL m_RdsMag[PHZBUF_SIZE]; - TYPEREAL m_RdsData[PHZBUF_SIZE]; - TYPEREAL m_RdsMatchCoef[PHZBUF_SIZE]; - TYPEREAL m_RdsLastSync; - TYPEREAL m_RdsLastSyncSlope; - TYPEREAL m_RdsLastData; - int m_MatchCoefLength; - CDownConvert m_RdsDownConvert; - CFir m_RdsBPFilter; - CFir m_RdsMatchedFilter; - CIir m_RdsBitSyncFilter; - TYPEREAL m_RdsOutputRate; - int m_RdsLastBit; - tRDS_GROUPS m_RdsGroupQueue[RDS_Q_SIZE]; - int m_RdsQHead; - int m_RdsQTail; - tRDS_GROUPS m_LastRdsGroup; - quint32 m_InBitStream; //input shift register for incoming raw data - int m_CurrentBlock; - int m_CurrentBitPosition; - int m_DecodeState; - int m_BGroupOffset; - int m_BlockErrors; - quint16 m_BlockData[4]; -}; - -#endif // WFMDEMOD_H +////////////////////////////////////////////////////////////////////// +// wfmdemod.h: interface for the CWFmDemod class. +// +// History: +// 2011-07-24 Initial creation MSW +// 2011-08-05 Initial release +///////////////////////////////////////////////////////////////////// +//========================================================================================== +// + + + This Software is released under the "Simplified BSD License" + + + +//Copyright 2010 Moe Wheatley. All rights reserved. +// +//Redistribution and use in source and binary forms, with or without modification, are +//permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other materials +// provided with the distribution. +// +//THIS SOFTWARE IS PROVIDED BY Moe Wheatley ``AS IS'' AND ANY EXPRESS OR IMPLIED +//WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +//FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Moe Wheatley OR +//CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +//CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +//SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +//ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +//NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +//ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +//The views and conclusions contained in the software and documentation are those of the +//authors and should not be interpreted as representing official policies, either expressed +//or implied, of Moe Wheatley. +//============================================================================= +#ifndef WFMDEMOD_H +#define WFMDEMOD_H +#include "datatypes.h" +#include "fir.h" +#include "iir.h" +#include "downconvert.h" +#include "rbdsconstants.h" + + +#define PHZBUF_SIZE 16384 + +#define RDS_Q_SIZE 100 + +class CWFmDemod +{ +public: + CWFmDemod(TYPEREAL samplerate); + virtual ~CWFmDemod(); + + TYPEREAL SetSampleRate(TYPEREAL samplerate, bool USver); + //overloaded functions for mono and stereo + int ProcessData(int InLength, TYPECPX* pInData, TYPECPX* pOutData); + int ProcessData(int InLength, TYPECPX* pInData, TYPEREAL* pOutData); + TYPEREAL GetDemodRate(){return m_OutRate;} + + bool GetNextRdsGroupData(tRDS_GROUPS* pGroupData); + int GetStereoLock(int* pPilotLock); + +private: + void InitDeemphasis( TYPEREAL Time, TYPEREAL SampleRate); //create De-emphasis LP filter + void ProcessDeemphasisFilter(int InLength, TYPEREAL* InBuf, TYPEREAL* OutBuf); + void ProcessDeemphasisFilter(int InLength, TYPECPX* InBuf, TYPECPX* OutBuf); + void InitPilotPll( TYPEREAL SampleRate ); + bool ProcessPilotPll( int InLength, TYPECPX* pInData ); + void InitRds( TYPEREAL SampleRate ); + void ProcessRdsPll( int InLength, TYPECPX* pInData, TYPEREAL* pOutData ); + inline TYPEREAL arctan2(TYPEREAL y, TYPEREAL x); + + void ProcessNewRdsBit(int bit); + quint32 CheckBlock(quint32 BlockOffset, int UseFec); + + TYPEREAL m_SampleRate; + TYPEREAL m_OutRate; + TYPEREAL m_RawFm[PHZBUF_SIZE]; + TYPECPX m_CpxRawFm[PHZBUF_SIZE]; + CDecimateBy2* m_pDecBy2A; + CDecimateBy2* m_pDecBy2B; + CDecimateBy2* m_pDecBy2C; + + TYPECPX m_D0; //complex delay line variables + TYPECPX m_D1; + + TYPEREAL m_DeemphasisAveRe; + TYPEREAL m_DeemphasisAveIm; + TYPEREAL m_DeemphasisAlpha; + + CIir m_MonoLPFilter; + CFir m_LPFilter; + CIir m_NotchFilter; + CIir m_PilotBPFilter; + CFir m_HilbertFilter; + + int m_PilotLocked; //variables for Pilot PLL + int m_LastPilotLocked; + TYPEREAL m_PilotNcoPhase; + TYPEREAL m_PilotNcoFreq; + TYPEREAL m_PilotNcoLLimit; + TYPEREAL m_PilotNcoHLimit; + TYPEREAL m_PilotPllAlpha; + TYPEREAL m_PilotPllBeta; + TYPEREAL m_PhaseErrorMagAve; + TYPEREAL m_PhaseErrorMagAlpha; + TYPEREAL m_PilotPhase[PHZBUF_SIZE]; + TYPEREAL m_PilotPhaseAdjust; + + TYPEREAL m_RdsNcoPhase; //variables for RDS PLL + TYPEREAL m_RdsNcoFreq; + TYPEREAL m_RdsNcoLLimit; + TYPEREAL m_RdsNcoHLimit; + TYPEREAL m_RdsPllAlpha; + TYPEREAL m_RdsPllBeta; + + TYPECPX m_RdsRaw[PHZBUF_SIZE]; //variables for RDS processing + TYPEREAL m_RdsMag[PHZBUF_SIZE]; + TYPEREAL m_RdsData[PHZBUF_SIZE]; + TYPEREAL m_RdsMatchCoef[PHZBUF_SIZE]; + TYPEREAL m_RdsLastSync; + TYPEREAL m_RdsLastSyncSlope; + TYPEREAL m_RdsLastData; + int m_MatchCoefLength; + CDownConvert m_RdsDownConvert; + CFir m_RdsBPFilter; + CFir m_RdsMatchedFilter; + CIir m_RdsBitSyncFilter; + TYPEREAL m_RdsOutputRate; + int m_RdsLastBit; + tRDS_GROUPS m_RdsGroupQueue[RDS_Q_SIZE]; + int m_RdsQHead; + int m_RdsQTail; + tRDS_GROUPS m_LastRdsGroup; + quint32 m_InBitStream; //input shift register for incoming raw data + int m_CurrentBlock; + int m_CurrentBitPosition; + int m_DecodeState; + int m_BGroupOffset; + int m_BlockErrors; + quint16 m_BlockData[4]; +}; + +#endif // WFMDEMOD_H diff --git a/src/fmstream.cpp b/src/fmstream.cpp index 43416c9..9b16440 100644 --- a/src/fmstream.cpp +++ b/src/fmstream.cpp @@ -1,599 +1,599 @@ -//--------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//--------------------------------------------------------------------------- - -#include "fmstream.h" - -#include "align.h" -#include "stdafx.h" -#include "string_exception.h" - -#include -#include -#include - -#pragma warning(push, 4) - -// fmstream::MAX_SAMPLE_QUEUE -// -// Maximum number of queued sample sets from the device -size_t const fmstream::MAX_SAMPLE_QUEUE = 200; // ~2sec - -// fmstream::STREAM_ID_AUDIO -// -// Stream identifier for the audio output stream -int const fmstream::STREAM_ID_AUDIO = 1; - -// fmstream::STREAM_ID_UECP -// -// Stream identifier for the UECP output stream -int const fmstream::STREAM_ID_UECP = 2; - -//--------------------------------------------------------------------------- -// fmstream Constructor (private) -// -// Arguments: -// -// device - RTL-SDR device instance -// tunerprops - Tuner device properties -// channelprops - Channel properties -// fmprops - FM digital signal processor properties - -fmstream::fmstream(std::unique_ptr device, - struct tunerprops const& tunerprops, - struct channelprops const& channelprops, - struct fmprops const& fmprops) - : m_device(std::move(device)), - m_decoderds(fmprops.decoderds), - m_rdsdecoder(fmprops.isnorthamerica), - m_muxname(generate_mux_name(channelprops)), - m_pcmsamplerate(fmprops.outputrate), - m_pcmgain(MPOW(10.0, (fmprops.outputgain / 10.0))) -{ - // The sample rate must be within 900001Hz - 3200000Hz - if ((fmprops.samplerate < 900001) || (fmprops.samplerate > 3200000)) - throw string_exception( - __func__, ": Tuner device sample rate must be in the range of 900001Hz to 3200000Hz"); - - // The only allowable output sample rates for this stream are 44100Hz and 48000Hz - if ((m_pcmsamplerate != 44100) && (m_pcmsamplerate != 48000)) - throw string_exception(__func__, - ": DSP output sample rate must be set to either 44.1KHz or 48.0KHz"); - - // Initialize the RTL-SDR device instance - m_device->set_frequency_correction(tunerprops.freqcorrection + channelprops.freqcorrection); - uint32_t samplerate = m_device->set_sample_rate(fmprops.samplerate); - uint32_t frequency = - m_device->set_center_frequency(channelprops.frequency + (samplerate / 4)); // DC offset - - // Initialize the demodulator parameters - // - tDemodInfo demodinfo = {}; - demodinfo.HiCutmax = 100000; - demodinfo.HiCut = 100000; - demodinfo.LowCut = -100000; - demodinfo.SquelchValue = -160; - demodinfo.WfmDownsampleQuality = static_cast(fmprops.downsamplequality); - - // Initialize the wideband FM demodulator - m_demodulator = std::unique_ptr(new CDemodulator()); - m_demodulator->SetUSFmVersion(fmprops.isnorthamerica); - m_demodulator->SetInputSampleRate(static_cast(samplerate)); - m_demodulator->SetDemod(DEMOD_WFM, demodinfo); - m_demodulator->SetDemodFreq(static_cast(frequency - channelprops.frequency)); - - // Initialize the output resampler - m_resampler = std::unique_ptr(new CFractResampler()); - m_resampler->Init(m_demodulator->GetInputBufferLimit()); - - // Adjust the device gain as specified by the channel properties - m_device->set_automatic_gain_control(channelprops.autogain); - if (channelprops.autogain == false) - m_device->set_gain(channelprops.manualgain); - - // Create a worker thread on which to perform the transfer operations - scalar_condition started{false}; - m_worker = std::thread(&fmstream::transfer, this, std::ref(started)); - started.wait_until_equals(true); -} - -//--------------------------------------------------------------------------- -// fmstream Destructor - -fmstream::~fmstream() -{ - close(); -} - -//--------------------------------------------------------------------------- -// fmstream::canseek -// -// Gets a flag indicating if the stream allows seek operations -// -// Arguments: -// -// NONE - -bool fmstream::canseek(void) const -{ - return false; -} - -//--------------------------------------------------------------------------- -// fmstream::close -// -// Closes the stream -// -// Arguments: -// -// NONE - -void fmstream::close(void) -{ - m_stop = true; // Signal worker thread to stop - if (m_device) - m_device->cancel_async(); // Cancel any async read operations - if (m_worker.joinable()) - m_worker.join(); // Wait for thread - m_device.reset(); // Release RTL-SDR device -} - -//--------------------------------------------------------------------------- -// fmstream::create (static) -// -// Factory method, creates a new fmstream instance -// -// Arguments: -// -// device - RTL-SDR device instance -// tunerprops - Tunder device properties -// channelprops - Channel properties -// fmprops - FM digital signal processor properties - -std::unique_ptr fmstream::create(std::unique_ptr device, - struct tunerprops const& tunerprops, - struct channelprops const& channelprops, - struct fmprops const& fmprops) -{ - return std::unique_ptr( - new fmstream(std::move(device), tunerprops, channelprops, fmprops)); -} - -//--------------------------------------------------------------------------- -// fmstream::demuxabort -// -// Aborts the demultiplexer -// -// Arguments: -// -// NONE - -void fmstream::demuxabort(void) -{ -} - -//--------------------------------------------------------------------------- -// fmstream::demuxflush -// -// Flushes the demultiplexer -// -// Arguments: -// -// NONE - -void fmstream::demuxflush(void) -{ -} - -//--------------------------------------------------------------------------- -// fmstream::demuxread -// -// Reads the next packet from the demultiplexer -// -// Arguments: -// -// allocator - DemuxPacket allocation function - -DEMUX_PACKET* fmstream::demuxread(std::function const& allocator) -{ - // If there is an RDS UECP packet available, handle it before demodulating more audio - uecp_data_packet uecp_packet; - if (m_rdsdecoder.pop_uecp_data_packet(uecp_packet) && (!uecp_packet.empty())) - { - - // The user may have opted to disable RDS. The packet from the decoder still - // needs to be popped from the queue, but don't do anything with it ... - if (m_decoderds) - { - - // Allocate and initialize the UECP demultiplexer packet - int packetsize = static_cast(uecp_packet.size()); - DEMUX_PACKET* packet = allocator(packetsize); - if (packet == nullptr) - return nullptr; - - packet->iStreamId = STREAM_ID_UECP; - packet->iSize = packetsize; - - // Copy the UECP data into the demultiplexer packet and return it - memcpy(packet->pData, uecp_packet.data(), uecp_packet.size()); - return packet; - } - } - - // Wait for there to be a packet of samples available for processing - std::unique_lock lock(m_queuelock); - m_cv.wait(lock, [&]() -> bool { return ((m_queue.size() > 0) || m_stopped.load() == true); }); - - // If the worker thread was stopped, check for and re-throw any exception that occurred, - // otherwise assume it was stopped normally and return an empty demultiplexer packet - if (m_stopped.load() == true) - { - - if (m_worker_exception) - std::rethrow_exception(m_worker_exception); - else - return allocator(0); - } - - // Pop off the topmost packet of samples from the queue<> and release the lock - std::unique_ptr samples(std::move(m_queue.front())); - m_queue.pop(); - lock.unlock(); - - // If the packet of samples is null, the writer has indicated there was a problem - if (!samples) - { - - m_dts = STREAM_TIME_BASE; // Reset the current decode time stamp - - // Create a STREAMCHANGE packet that has no data - DEMUX_PACKET* packet = allocator(0); - if (packet) - packet->iStreamId = DEMUX_SPECIALID_STREAMCHANGE; - - return packet; // Return the generated packet - } - - // Process the I/Q data, the original samples buffer can be reused/overwritten as it's processed - int audiopackets = m_demodulator->ProcessData(m_demodulator->GetInputBufferLimit(), samples.get(), - samples.get()); - - // Process any RDS group data that was collected during demodulation - tRDS_GROUPS rdsgroup = {}; - while (m_demodulator->GetNextRdsGroupData(&rdsgroup)) - m_rdsdecoder.decode_rdsgroup(rdsgroup); - - // Determine the size of the demultiplexer packet data and allocate it - int packetsize = audiopackets * sizeof(TYPESTEREO16); - DEMUX_PACKET* packet = allocator(packetsize); - if (packet == nullptr) - return nullptr; - - // Resample the audio data directly into the allocated packet buffer - audiopackets = m_resampler->Resample( - audiopackets, (m_demodulator->GetOutputRate() / m_pcmsamplerate), samples.get(), - reinterpret_cast(packet->pData), m_pcmgain); - - // Calculate the proper duration for the packet - double duration = (audiopackets / static_cast(m_pcmsamplerate)) * STREAM_TIME_BASE; - - // Set up the demultiplexer packet with the proper size, duration and dts - packet->iStreamId = STREAM_ID_AUDIO; - packet->iSize = audiopackets * sizeof(TYPESTEREO16); - packet->duration = duration; - packet->dts = packet->pts = m_dts; - - // Increment the decode time stamp value based on the calculated duration - m_dts += duration; - - return packet; -} - -//--------------------------------------------------------------------------- -// fmstream::demuxreset -// -// Resets the demultiplexer -// -// Arguments: -// -// NONE - -void fmstream::demuxreset(void) -{ -} - -//--------------------------------------------------------------------------- -// fmstream::devicename -// -// Gets the device name associated with the stream -// -// Arguments: -// -// NONE - -std::string fmstream::devicename(void) const -{ - return std::string(m_device->get_device_name()); -} - -//--------------------------------------------------------------------------- -// fmstream::enumproperties -// -// Enumerates the stream properties -// -// Arguments: -// -// callback - Callback to invoke for each stream - -void fmstream::enumproperties(std::function const& callback) -{ - // AUDIO STREAM - // - streamprops audio = {}; - audio.codec = "pcm_s16le"; - audio.pid = STREAM_ID_AUDIO; - audio.channels = 2; - audio.samplerate = static_cast(m_pcmsamplerate); - audio.bitspersample = 16; - callback(audio); - - // UECP STREAM - // - if (m_decoderds) - { - - streamprops uecp = {}; - uecp.codec = "rds"; - uecp.pid = STREAM_ID_UECP; - callback(uecp); - } -} - -//--------------------------------------------------------------------------- -// fmstream::generate_mux_name (private) -// -// Generates the mux name to associate with the stream -// -// Arguments: -// -// channelprops - Channel properties structure - -std::string fmstream::generate_mux_name(struct channelprops const& channelprops) const -{ - // Set the default mux name to the frequency in Megahertz - char buf[64] = {0}; - snprintf(buf, std::extent::value, "%.1f FM", - (static_cast(channelprops.frequency) / 1000000.0f)); - return std::string(buf); -} - -//--------------------------------------------------------------------------- -// fmstream::length -// -// Gets the length of the stream; or -1 if stream is real-time -// -// Arguments: -// -// NONE - -long long fmstream::length(void) const -{ - return -1; -} - -//--------------------------------------------------------------------------- -// fmstream::muxname -// -// Gets the mux name associated with the stream -// -// Arguments: -// -// NONE - -std::string fmstream::muxname(void) const -{ - // If the callsign for the station is known, use that with an -FM suffix, otherwise use the default - return (m_rdsdecoder.has_rbds_callsign()) ? m_rdsdecoder.get_rbds_callsign() : m_muxname; -} - -//--------------------------------------------------------------------------- -// fmstream::position -// -// Gets the current position of the stream -// -// Arguments: -// -// NONE - -long long fmstream::position(void) const -{ - return -1; -} - -//--------------------------------------------------------------------------- -// fmstream::read -// -// Reads data from the live stream -// -// Arguments: -// -// buffer - Buffer to receive the live stream data -// count - Size of the destination buffer in bytes - -size_t fmstream::read(uint8_t* /*buffer*/, size_t /*count*/) -{ - return 0; -} - -//--------------------------------------------------------------------------- -// fmstream::realtime -// -// Gets a flag indicating if the stream is real-time -// -// Arguments: -// -// NONE - -bool fmstream::realtime(void) const -{ - return true; -} - -//--------------------------------------------------------------------------- -// fmstream::seek -// -// Sets the stream pointer to a specific position -// -// Arguments: -// -// position - Delta within the stream to seek, relative to whence -// whence - Starting position from which to apply the delta - -long long fmstream::seek(long long /*position*/, int /*whence*/) -{ - return -1; -} - -//--------------------------------------------------------------------------- -// fmstream::servicename -// -// Gets the service name associated with the stream -// -// Arguments: -// -// NONE - -std::string fmstream::servicename(void) const -{ - return std::string("Wideband FM radio"); -} - -//--------------------------------------------------------------------------- -// fmstream::signalquality -// -// Gets the signal quality as a percentage -// -// Arguments: -// -// NONE - -void fmstream::signalquality(int& quality, int& snr) const -{ - TYPEREAL demodquality = 0; - TYPEREAL demodsnr = 0; - - m_demodulator->GetSignalLevels(demodquality, demodsnr); - - // For wideband FM, adjust the range such that 80% is nominal for - // signal quality and 60% is nominal for signal-to-noise; this - // adjustment is based on observation and (perceived) output quality - quality = std::max(0, std::min(100, static_cast(100.0 * (demodquality / 0.80)))); - snr = std::max(0, std::min(100, static_cast(100.0 * (demodsnr / 0.60)))); -} - -//--------------------------------------------------------------------------- -// fmstream::transfer (private) -// -// Worker thread procedure used to transfer data into the ring buffer -// -// Arguments: -// -// started - Condition variable to set when thread has started - -void fmstream::transfer(scalar_condition& started) -{ - assert(m_demodulator); - assert(m_device); - - // The I/Q samples from the device come in as a pair of 8 bit unsigned integers - size_t const readsize = m_demodulator->GetInputBufferLimit() * 2; - - // read_callback_func (local) - // - // Asynchronous read callback function for the RTL-SDR device - auto read_callback_func = [&](uint8_t const* buffer, size_t count) -> void - { - std::unique_ptr samples; // Array of I/Q samples to return - - // If the proper amount of data was returned by the callback, convert it into - // the floating-point I/Q sample data for the demodulator to process - if (count == readsize) - { - - samples = std::unique_ptr(new TYPECPX[readsize / 2]); - for (int index = 0; index < m_demodulator->GetInputBufferLimit(); index++) - { - - // The demodulator expects the I/Q samples in the range of -32767.0 through +32767.0 - // (32767.0 / 127.5) = 256.9960784313725 - samples[index] = { - -#ifdef FMDSP_USE_DOUBLE_PRECISION - (static_cast(buffer[(index * 2)]) - 127.5) * 256.9960784313725, // I - (static_cast(buffer[(index * 2) + 1]) - 127.5) * 256.9960784313725, // Q -#else - (static_cast(buffer[(index * 2)]) - 127.5f) * 256.9960784313725f, // I - (static_cast(buffer[(index * 2) + 1]) - 127.5f) * 256.9960784313725f, // Q -#endif - }; - } - } - - // Push the converted samples into the queue<> for processing. If there is insufficient space - // left in the queue<>, the samples aren't being processed quickly enough to keep up with the rate - std::unique_lock lock(m_queuelock); - if (m_queue.size() < MAX_SAMPLE_QUEUE) - m_queue.emplace(std::move(samples)); - else - { - - m_queue = sample_queue_t(); // Replace the queue<> - m_queue.push(nullptr); // Push a resync packet (null) - if (samples) - m_queue.emplace(std::move(samples)); // Push samples - } - - // Notify any threads waiting on the lock that the queue<> has been updated - m_cv.notify_all(); - }; - - // Begin streaming from the device and inform the caller that the thread is running - m_device->begin_stream(); - started = true; - - // Continuously read data from the device until cancel_async() has been called - try - { - m_device->read_async(read_callback_func, static_cast(readsize)); - } - catch (...) - { - m_worker_exception = std::current_exception(); - } - - m_stopped.store(true); // Thread is stopped - m_cv.notify_all(); // Unblock any waiters -} - -//--------------------------------------------------------------------------- - -#pragma warning(pop) +//--------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//--------------------------------------------------------------------------- + +#include "fmstream.h" + +#include "align.h" +#include "stdafx.h" +#include "string_exception.h" + +#include +#include +#include + +#pragma warning(push, 4) + +// fmstream::MAX_SAMPLE_QUEUE +// +// Maximum number of queued sample sets from the device +size_t const fmstream::MAX_SAMPLE_QUEUE = 200; // ~2sec + +// fmstream::STREAM_ID_AUDIO +// +// Stream identifier for the audio output stream +int const fmstream::STREAM_ID_AUDIO = 1; + +// fmstream::STREAM_ID_UECP +// +// Stream identifier for the UECP output stream +int const fmstream::STREAM_ID_UECP = 2; + +//--------------------------------------------------------------------------- +// fmstream Constructor (private) +// +// Arguments: +// +// device - RTL-SDR device instance +// tunerprops - Tuner device properties +// channelprops - Channel properties +// fmprops - FM digital signal processor properties + +fmstream::fmstream(std::unique_ptr device, + struct tunerprops const& tunerprops, + struct channelprops const& channelprops, + struct fmprops const& fmprops) + : m_device(std::move(device)), + m_decoderds(fmprops.decoderds), + m_rdsdecoder(fmprops.isnorthamerica), + m_muxname(generate_mux_name(channelprops)), + m_pcmsamplerate(fmprops.outputrate), + m_pcmgain(MPOW(10.0, (fmprops.outputgain / 10.0))) +{ + // The sample rate must be within 900001Hz - 3200000Hz + if ((fmprops.samplerate < 900001) || (fmprops.samplerate > 3200000)) + throw string_exception( + __func__, ": Tuner device sample rate must be in the range of 900001Hz to 3200000Hz"); + + // The only allowable output sample rates for this stream are 44100Hz and 48000Hz + if ((m_pcmsamplerate != 44100) && (m_pcmsamplerate != 48000)) + throw string_exception(__func__, + ": DSP output sample rate must be set to either 44.1KHz or 48.0KHz"); + + // Initialize the RTL-SDR device instance + m_device->set_frequency_correction(tunerprops.freqcorrection + channelprops.freqcorrection); + uint32_t samplerate = m_device->set_sample_rate(fmprops.samplerate); + uint32_t frequency = + m_device->set_center_frequency(channelprops.frequency + (samplerate / 4)); // DC offset + + // Initialize the demodulator parameters + // + tDemodInfo demodinfo = {}; + demodinfo.HiCutmax = 100000; + demodinfo.HiCut = 100000; + demodinfo.LowCut = -100000; + demodinfo.SquelchValue = -160; + demodinfo.WfmDownsampleQuality = static_cast(fmprops.downsamplequality); + + // Initialize the wideband FM demodulator + m_demodulator = std::unique_ptr(new CDemodulator()); + m_demodulator->SetUSFmVersion(fmprops.isnorthamerica); + m_demodulator->SetInputSampleRate(static_cast(samplerate)); + m_demodulator->SetDemod(DEMOD_WFM, demodinfo); + m_demodulator->SetDemodFreq(static_cast(frequency - channelprops.frequency)); + + // Initialize the output resampler + m_resampler = std::unique_ptr(new CFractResampler()); + m_resampler->Init(m_demodulator->GetInputBufferLimit()); + + // Adjust the device gain as specified by the channel properties + m_device->set_automatic_gain_control(channelprops.autogain); + if (channelprops.autogain == false) + m_device->set_gain(channelprops.manualgain); + + // Create a worker thread on which to perform the transfer operations + scalar_condition started{false}; + m_worker = std::thread(&fmstream::transfer, this, std::ref(started)); + started.wait_until_equals(true); +} + +//--------------------------------------------------------------------------- +// fmstream Destructor + +fmstream::~fmstream() +{ + close(); +} + +//--------------------------------------------------------------------------- +// fmstream::canseek +// +// Gets a flag indicating if the stream allows seek operations +// +// Arguments: +// +// NONE + +bool fmstream::canseek(void) const +{ + return false; +} + +//--------------------------------------------------------------------------- +// fmstream::close +// +// Closes the stream +// +// Arguments: +// +// NONE + +void fmstream::close(void) +{ + m_stop = true; // Signal worker thread to stop + if (m_device) + m_device->cancel_async(); // Cancel any async read operations + if (m_worker.joinable()) + m_worker.join(); // Wait for thread + m_device.reset(); // Release RTL-SDR device +} + +//--------------------------------------------------------------------------- +// fmstream::create (static) +// +// Factory method, creates a new fmstream instance +// +// Arguments: +// +// device - RTL-SDR device instance +// tunerprops - Tunder device properties +// channelprops - Channel properties +// fmprops - FM digital signal processor properties + +std::unique_ptr fmstream::create(std::unique_ptr device, + struct tunerprops const& tunerprops, + struct channelprops const& channelprops, + struct fmprops const& fmprops) +{ + return std::unique_ptr( + new fmstream(std::move(device), tunerprops, channelprops, fmprops)); +} + +//--------------------------------------------------------------------------- +// fmstream::demuxabort +// +// Aborts the demultiplexer +// +// Arguments: +// +// NONE + +void fmstream::demuxabort(void) +{ +} + +//--------------------------------------------------------------------------- +// fmstream::demuxflush +// +// Flushes the demultiplexer +// +// Arguments: +// +// NONE + +void fmstream::demuxflush(void) +{ +} + +//--------------------------------------------------------------------------- +// fmstream::demuxread +// +// Reads the next packet from the demultiplexer +// +// Arguments: +// +// allocator - DemuxPacket allocation function + +DEMUX_PACKET* fmstream::demuxread(std::function const& allocator) +{ + // If there is an RDS UECP packet available, handle it before demodulating more audio + uecp_data_packet uecp_packet; + if (m_rdsdecoder.pop_uecp_data_packet(uecp_packet) && (!uecp_packet.empty())) + { + + // The user may have opted to disable RDS. The packet from the decoder still + // needs to be popped from the queue, but don't do anything with it ... + if (m_decoderds) + { + + // Allocate and initialize the UECP demultiplexer packet + int packetsize = static_cast(uecp_packet.size()); + DEMUX_PACKET* packet = allocator(packetsize); + if (packet == nullptr) + return nullptr; + + packet->iStreamId = STREAM_ID_UECP; + packet->iSize = packetsize; + + // Copy the UECP data into the demultiplexer packet and return it + memcpy(packet->pData, uecp_packet.data(), uecp_packet.size()); + return packet; + } + } + + // Wait for there to be a packet of samples available for processing + std::unique_lock lock(m_queuelock); + m_cv.wait(lock, [&]() -> bool { return ((m_queue.size() > 0) || m_stopped.load() == true); }); + + // If the worker thread was stopped, check for and re-throw any exception that occurred, + // otherwise assume it was stopped normally and return an empty demultiplexer packet + if (m_stopped.load() == true) + { + + if (m_worker_exception) + std::rethrow_exception(m_worker_exception); + else + return allocator(0); + } + + // Pop off the topmost packet of samples from the queue<> and release the lock + std::unique_ptr samples(std::move(m_queue.front())); + m_queue.pop(); + lock.unlock(); + + // If the packet of samples is null, the writer has indicated there was a problem + if (!samples) + { + + m_dts = STREAM_TIME_BASE; // Reset the current decode time stamp + + // Create a STREAMCHANGE packet that has no data + DEMUX_PACKET* packet = allocator(0); + if (packet) + packet->iStreamId = DEMUX_SPECIALID_STREAMCHANGE; + + return packet; // Return the generated packet + } + + // Process the I/Q data, the original samples buffer can be reused/overwritten as it's processed + int audiopackets = m_demodulator->ProcessData(m_demodulator->GetInputBufferLimit(), samples.get(), + samples.get()); + + // Process any RDS group data that was collected during demodulation + tRDS_GROUPS rdsgroup = {}; + while (m_demodulator->GetNextRdsGroupData(&rdsgroup)) + m_rdsdecoder.decode_rdsgroup(rdsgroup); + + // Determine the size of the demultiplexer packet data and allocate it + int packetsize = audiopackets * sizeof(TYPESTEREO16); + DEMUX_PACKET* packet = allocator(packetsize); + if (packet == nullptr) + return nullptr; + + // Resample the audio data directly into the allocated packet buffer + audiopackets = m_resampler->Resample( + audiopackets, (m_demodulator->GetOutputRate() / m_pcmsamplerate), samples.get(), + reinterpret_cast(packet->pData), m_pcmgain); + + // Calculate the proper duration for the packet + double duration = (audiopackets / static_cast(m_pcmsamplerate)) * STREAM_TIME_BASE; + + // Set up the demultiplexer packet with the proper size, duration and dts + packet->iStreamId = STREAM_ID_AUDIO; + packet->iSize = audiopackets * sizeof(TYPESTEREO16); + packet->duration = duration; + packet->dts = packet->pts = m_dts; + + // Increment the decode time stamp value based on the calculated duration + m_dts += duration; + + return packet; +} + +//--------------------------------------------------------------------------- +// fmstream::demuxreset +// +// Resets the demultiplexer +// +// Arguments: +// +// NONE + +void fmstream::demuxreset(void) +{ +} + +//--------------------------------------------------------------------------- +// fmstream::devicename +// +// Gets the device name associated with the stream +// +// Arguments: +// +// NONE + +std::string fmstream::devicename(void) const +{ + return std::string(m_device->get_device_name()); +} + +//--------------------------------------------------------------------------- +// fmstream::enumproperties +// +// Enumerates the stream properties +// +// Arguments: +// +// callback - Callback to invoke for each stream + +void fmstream::enumproperties(std::function const& callback) +{ + // AUDIO STREAM + // + streamprops audio = {}; + audio.codec = "pcm_s16le"; + audio.pid = STREAM_ID_AUDIO; + audio.channels = 2; + audio.samplerate = static_cast(m_pcmsamplerate); + audio.bitspersample = 16; + callback(audio); + + // UECP STREAM + // + if (m_decoderds) + { + + streamprops uecp = {}; + uecp.codec = "rds"; + uecp.pid = STREAM_ID_UECP; + callback(uecp); + } +} + +//--------------------------------------------------------------------------- +// fmstream::generate_mux_name (private) +// +// Generates the mux name to associate with the stream +// +// Arguments: +// +// channelprops - Channel properties structure + +std::string fmstream::generate_mux_name(struct channelprops const& channelprops) const +{ + // Set the default mux name to the frequency in Megahertz + char buf[64] = {0}; + snprintf(buf, std::extent::value, "%.1f FM", + (static_cast(channelprops.frequency) / 1000000.0f)); + return std::string(buf); +} + +//--------------------------------------------------------------------------- +// fmstream::length +// +// Gets the length of the stream; or -1 if stream is real-time +// +// Arguments: +// +// NONE + +long long fmstream::length(void) const +{ + return -1; +} + +//--------------------------------------------------------------------------- +// fmstream::muxname +// +// Gets the mux name associated with the stream +// +// Arguments: +// +// NONE + +std::string fmstream::muxname(void) const +{ + // If the callsign for the station is known, use that with an -FM suffix, otherwise use the default + return (m_rdsdecoder.has_rbds_callsign()) ? m_rdsdecoder.get_rbds_callsign() : m_muxname; +} + +//--------------------------------------------------------------------------- +// fmstream::position +// +// Gets the current position of the stream +// +// Arguments: +// +// NONE + +long long fmstream::position(void) const +{ + return -1; +} + +//--------------------------------------------------------------------------- +// fmstream::read +// +// Reads data from the live stream +// +// Arguments: +// +// buffer - Buffer to receive the live stream data +// count - Size of the destination buffer in bytes + +size_t fmstream::read(uint8_t* /*buffer*/, size_t /*count*/) +{ + return 0; +} + +//--------------------------------------------------------------------------- +// fmstream::realtime +// +// Gets a flag indicating if the stream is real-time +// +// Arguments: +// +// NONE + +bool fmstream::realtime(void) const +{ + return true; +} + +//--------------------------------------------------------------------------- +// fmstream::seek +// +// Sets the stream pointer to a specific position +// +// Arguments: +// +// position - Delta within the stream to seek, relative to whence +// whence - Starting position from which to apply the delta + +long long fmstream::seek(long long /*position*/, int /*whence*/) +{ + return -1; +} + +//--------------------------------------------------------------------------- +// fmstream::servicename +// +// Gets the service name associated with the stream +// +// Arguments: +// +// NONE + +std::string fmstream::servicename(void) const +{ + return std::string("Wideband FM radio"); +} + +//--------------------------------------------------------------------------- +// fmstream::signalquality +// +// Gets the signal quality as a percentage +// +// Arguments: +// +// NONE + +void fmstream::signalquality(int& quality, int& snr) const +{ + TYPEREAL demodquality = 0; + TYPEREAL demodsnr = 0; + + m_demodulator->GetSignalLevels(demodquality, demodsnr); + + // For wideband FM, adjust the range such that 80% is nominal for + // signal quality and 60% is nominal for signal-to-noise; this + // adjustment is based on observation and (perceived) output quality + quality = std::max(0, std::min(100, static_cast(100.0 * (demodquality / 0.80)))); + snr = std::max(0, std::min(100, static_cast(100.0 * (demodsnr / 0.60)))); +} + +//--------------------------------------------------------------------------- +// fmstream::transfer (private) +// +// Worker thread procedure used to transfer data into the ring buffer +// +// Arguments: +// +// started - Condition variable to set when thread has started + +void fmstream::transfer(scalar_condition& started) +{ + assert(m_demodulator); + assert(m_device); + + // The I/Q samples from the device come in as a pair of 8 bit unsigned integers + size_t const readsize = m_demodulator->GetInputBufferLimit() * 2; + + // read_callback_func (local) + // + // Asynchronous read callback function for the RTL-SDR device + auto read_callback_func = [&](uint8_t const* buffer, size_t count) -> void + { + std::unique_ptr samples; // Array of I/Q samples to return + + // If the proper amount of data was returned by the callback, convert it into + // the floating-point I/Q sample data for the demodulator to process + if (count == readsize) + { + + samples = std::unique_ptr(new TYPECPX[readsize / 2]); + for (int index = 0; index < m_demodulator->GetInputBufferLimit(); index++) + { + + // The demodulator expects the I/Q samples in the range of -32767.0 through +32767.0 + // (32767.0 / 127.5) = 256.9960784313725 + samples[index] = { + +#ifdef FMDSP_USE_DOUBLE_PRECISION + (static_cast(buffer[(index * 2)]) - 127.5) * 256.9960784313725, // I + (static_cast(buffer[(index * 2) + 1]) - 127.5) * 256.9960784313725, // Q +#else + (static_cast(buffer[(index * 2)]) - 127.5f) * 256.9960784313725f, // I + (static_cast(buffer[(index * 2) + 1]) - 127.5f) * 256.9960784313725f, // Q +#endif + }; + } + } + + // Push the converted samples into the queue<> for processing. If there is insufficient space + // left in the queue<>, the samples aren't being processed quickly enough to keep up with the rate + std::unique_lock lock(m_queuelock); + if (m_queue.size() < MAX_SAMPLE_QUEUE) + m_queue.emplace(std::move(samples)); + else + { + + m_queue = sample_queue_t(); // Replace the queue<> + m_queue.push(nullptr); // Push a resync packet (null) + if (samples) + m_queue.emplace(std::move(samples)); // Push samples + } + + // Notify any threads waiting on the lock that the queue<> has been updated + m_cv.notify_all(); + }; + + // Begin streaming from the device and inform the caller that the thread is running + m_device->begin_stream(); + started = true; + + // Continuously read data from the device until cancel_async() has been called + try + { + m_device->read_async(read_callback_func, static_cast(readsize)); + } + catch (...) + { + m_worker_exception = std::current_exception(); + } + + m_stopped.store(true); // Thread is stopped + m_cv.notify_all(); // Unblock any waiters +} + +//--------------------------------------------------------------------------- + +#pragma warning(pop) diff --git a/src/fmstream.h b/src/fmstream.h index edcb7a0..d012fa9 100644 --- a/src/fmstream.h +++ b/src/fmstream.h @@ -1,229 +1,229 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//----------------------------------------------------------------------------- - -#ifndef __FMSTREAM_H_ -#define __FMSTREAM_H_ -#pragma once - -#include "fmdsp/demodulator.h" -#include "fmdsp/fractresampler.h" -#include "props.h" -#include "pvrstream.h" -#include "rdsdecoder.h" -#include "rtldevice.h" -#include "scalar_condition.h" - -#include -#include -#include -#include -#include -#include - -#pragma warning(push, 4) - -//--------------------------------------------------------------------------- -// Class fmstream -// -// Implements a FM radio stream - -class fmstream : public pvrstream -{ -public: - // Destructor - // - virtual ~fmstream(); - - //----------------------------------------------------------------------- - // Member Functions - - // canseek - // - // Flag indicating if the stream allows seek operations - bool canseek(void) const override; - - // close - // - // Closes the stream - void close(void) override; - - // create (static) - // - // Factory method, creates a new fmstream instance - static std::unique_ptr create(std::unique_ptr device, - struct tunerprops const& tunerprops, - struct channelprops const& channelprops, - struct fmprops const& fmprops); - - // demuxabort - // - // Aborts the demultiplexer - void demuxabort(void) override; - - // demuxflush - // - // Flushes the demultiplexer - void demuxflush(void) override; - - // demuxread - // - // Reads the next packet from the demultiplexer - DEMUX_PACKET* demuxread(std::function const& allocator) override; - - // demuxreset - // - // Resets the demultiplexer - void demuxreset(void) override; - - // devicename - // - // Gets the device name associated with the stream - std::string devicename(void) const override; - - // enumproperties - // - // Enumerates the stream properties - void enumproperties( - std::function const& callback) override; - - // length - // - // Gets the length of the stream - long long length(void) const override; - - // muxname - // - // Gets the mux name associated with the stream - std::string muxname(void) const override; - - // position - // - // Gets the current position of the stream - long long position(void) const override; - - // read - // - // Reads available data from the stream - size_t read(uint8_t* buffer, size_t count) override; - - // realtime - // - // Gets a flag indicating if the stream is real-time - bool realtime(void) const override; - - // seek - // - // Sets the stream pointer to a specific position - long long seek(long long position, int whence) override; - - // servicename - // - // Gets the service name associated with the stream - std::string servicename(void) const override; - - // signalquality - // - // Gets the signal quality as percentages - void signalquality(int& quality, int& snr) const override; - -private: - fmstream(fmstream const&) = delete; - fmstream& operator=(fmstream const&) = delete; - - // MAX_SAMPLE_QUEUE - // - // Maximum number of queued sample sets from device - static size_t const MAX_SAMPLE_QUEUE; - - // STREAM_ID_AUDIO - // - // Stream identifier for the audio output stream - static int const STREAM_ID_AUDIO; - - // STREAM_ID_UECP - // - // Stream identifier for the UECP output stream - static int const STREAM_ID_UECP; - - // Instance Constructor - // - fmstream(std::unique_ptr device, - struct tunerprops const& tunerprops, - struct channelprops const& channelprops, - struct fmprops const& fmprops); - - //----------------------------------------------------------------------- - // Private Type Declarations - - // sample_queue_item_t - // - // Defines the type of a single sample_queue_t entry - using sample_queue_item_t = std::unique_ptr; - - // sample_queue_t - // - // Defines the type of the input sample queue - using sample_queue_t = std::queue; - - //----------------------------------------------------------------------- - // Private Member Functions - - // generate_mux_name - // - // Generates the mux name to associate with the stream - std::string generate_mux_name(struct channelprops const& channelprops) const; - - // transfer - // - // Worker thread procedure used to transfer data into the ring buffer - void transfer(scalar_condition& started); - - //----------------------------------------------------------------------- - // Member Variables - - std::unique_ptr m_device; // RTL-SDR device instance - std::unique_ptr m_demodulator; // CuteSDR demodulator instance - std::unique_ptr m_resampler; // CuteSDR resampler instance - bool const m_decoderds; // Flag to send decoded RDS data - rdsdecoder m_rdsdecoder; // RDS decoder instance - - std::string const m_muxname; // Default mux name for the stream - uint32_t const m_pcmsamplerate; // Output sample rate - TYPEREAL const m_pcmgain; // Output gain - double m_dts{STREAM_TIME_BASE}; // Current decode time stamp - - // STREAM CONTROL - // - sample_queue_t m_queue; // queue<> of prepared samples - mutable std::mutex m_queuelock; // Synchronization object - std::condition_variable m_cv; // Transfer event condvar - std::thread m_worker; // Data transfer thread - std::exception_ptr m_worker_exception; // Exception on worker thread - scalar_condition m_stop{false}; // Condition to stop data transfer - std::atomic m_stopped{false}; // Data transfer stopped flag -}; - -//----------------------------------------------------------------------------- - -#pragma warning(pop) - -#endif // __FMSTREAM_H_ +//----------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef __FMSTREAM_H_ +#define __FMSTREAM_H_ +#pragma once + +#include "fmdsp/demodulator.h" +#include "fmdsp/fractresampler.h" +#include "props.h" +#include "pvrstream.h" +#include "rdsdecoder.h" +#include "rtldevice.h" +#include "scalar_condition.h" + +#include +#include +#include +#include +#include +#include + +#pragma warning(push, 4) + +//--------------------------------------------------------------------------- +// Class fmstream +// +// Implements a FM radio stream + +class fmstream : public pvrstream +{ +public: + // Destructor + // + virtual ~fmstream(); + + //----------------------------------------------------------------------- + // Member Functions + + // canseek + // + // Flag indicating if the stream allows seek operations + bool canseek(void) const override; + + // close + // + // Closes the stream + void close(void) override; + + // create (static) + // + // Factory method, creates a new fmstream instance + static std::unique_ptr create(std::unique_ptr device, + struct tunerprops const& tunerprops, + struct channelprops const& channelprops, + struct fmprops const& fmprops); + + // demuxabort + // + // Aborts the demultiplexer + void demuxabort(void) override; + + // demuxflush + // + // Flushes the demultiplexer + void demuxflush(void) override; + + // demuxread + // + // Reads the next packet from the demultiplexer + DEMUX_PACKET* demuxread(std::function const& allocator) override; + + // demuxreset + // + // Resets the demultiplexer + void demuxreset(void) override; + + // devicename + // + // Gets the device name associated with the stream + std::string devicename(void) const override; + + // enumproperties + // + // Enumerates the stream properties + void enumproperties( + std::function const& callback) override; + + // length + // + // Gets the length of the stream + long long length(void) const override; + + // muxname + // + // Gets the mux name associated with the stream + std::string muxname(void) const override; + + // position + // + // Gets the current position of the stream + long long position(void) const override; + + // read + // + // Reads available data from the stream + size_t read(uint8_t* buffer, size_t count) override; + + // realtime + // + // Gets a flag indicating if the stream is real-time + bool realtime(void) const override; + + // seek + // + // Sets the stream pointer to a specific position + long long seek(long long position, int whence) override; + + // servicename + // + // Gets the service name associated with the stream + std::string servicename(void) const override; + + // signalquality + // + // Gets the signal quality as percentages + void signalquality(int& quality, int& snr) const override; + +private: + fmstream(fmstream const&) = delete; + fmstream& operator=(fmstream const&) = delete; + + // MAX_SAMPLE_QUEUE + // + // Maximum number of queued sample sets from device + static size_t const MAX_SAMPLE_QUEUE; + + // STREAM_ID_AUDIO + // + // Stream identifier for the audio output stream + static int const STREAM_ID_AUDIO; + + // STREAM_ID_UECP + // + // Stream identifier for the UECP output stream + static int const STREAM_ID_UECP; + + // Instance Constructor + // + fmstream(std::unique_ptr device, + struct tunerprops const& tunerprops, + struct channelprops const& channelprops, + struct fmprops const& fmprops); + + //----------------------------------------------------------------------- + // Private Type Declarations + + // sample_queue_item_t + // + // Defines the type of a single sample_queue_t entry + using sample_queue_item_t = std::unique_ptr; + + // sample_queue_t + // + // Defines the type of the input sample queue + using sample_queue_t = std::queue; + + //----------------------------------------------------------------------- + // Private Member Functions + + // generate_mux_name + // + // Generates the mux name to associate with the stream + std::string generate_mux_name(struct channelprops const& channelprops) const; + + // transfer + // + // Worker thread procedure used to transfer data into the ring buffer + void transfer(scalar_condition& started); + + //----------------------------------------------------------------------- + // Member Variables + + std::unique_ptr m_device; // RTL-SDR device instance + std::unique_ptr m_demodulator; // CuteSDR demodulator instance + std::unique_ptr m_resampler; // CuteSDR resampler instance + bool const m_decoderds; // Flag to send decoded RDS data + rdsdecoder m_rdsdecoder; // RDS decoder instance + + std::string const m_muxname; // Default mux name for the stream + uint32_t const m_pcmsamplerate; // Output sample rate + TYPEREAL const m_pcmgain; // Output gain + double m_dts{STREAM_TIME_BASE}; // Current decode time stamp + + // STREAM CONTROL + // + sample_queue_t m_queue; // queue<> of prepared samples + mutable std::mutex m_queuelock; // Synchronization object + std::condition_variable m_cv; // Transfer event condvar + std::thread m_worker; // Data transfer thread + std::exception_ptr m_worker_exception; // Exception on worker thread + scalar_condition m_stop{false}; // Condition to stop data transfer + std::atomic m_stopped{false}; // Data transfer stopped flag +}; + +//----------------------------------------------------------------------------- + +#pragma warning(pop) + +#endif // __FMSTREAM_H_ diff --git a/src/hddsp/acquire.c b/src/hddsp/acquire.c index bf8bd1e..9940a79 100644 --- a/src/hddsp/acquire.c +++ b/src/hddsp/acquire.c @@ -1,372 +1,372 @@ -/* - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#ifdef _MSC_VER -#define _USE_MATH_DEFINES -#endif - -#include -#include - -#include "acquire.h" -#include "defines.h" -#include "input.h" - -#define FILTER_DELAY 15 -#define DECIMATION_FACTOR_FM 2 -#define DECIMATION_FACTOR_AM 32 - -static float filter_taps_fm[] = { - -0.000685643230099231, - 0.005636964458972216, - 0.009015781804919243, - -0.015486305579543114, - -0.035108357667922974, - 0.017446253448724747, - 0.08155813068151474, - 0.007995186373591423, - -0.13311293721199036, - -0.0727422907948494, - 0.15914097428321838, - 0.16498781740665436, - -0.1324498951435089, - -0.2484012246131897, - 0.051773931831121445, - 0.2821577787399292, - 0.051773931831121445, - -0.2484012246131897, - -0.1324498951435089, - 0.16498781740665436, - 0.15914097428321838, - -0.0727422907948494, - -0.13311293721199036, - 0.007995186373591423, - 0.08155813068151474, - 0.017446253448724747, - -0.035108357667922974, - -0.015486305579543114, - 0.009015781804919243, - 0.005636964458972216, - -0.000685643230099231, - 0 -}; - -static float filter_taps_am[] = { - -0.00038464731187559664, - -0.00021618751634377986, - 0.0026779419276863337, - -0.00029802651260979474, - -0.0012626448879018426, - -0.0013182522961869836, - -0.012252614833414555, - 0.015980124473571777, - 0.037112727761268616, - -0.05451361835002899, - -0.05804193392395973, - 0.11320608854293823, - 0.055298302322626114, - -0.16878043115139008, - -0.022917453199625015, - 0.19178225100040436, - -0.022917453199625015, - -0.16878043115139008, - 0.055298302322626114, - 0.11320608854293823, - -0.05804193392395973, - -0.05451361835002899, - 0.037112727761268616, - 0.015980124473571777, - -0.012252614833414555, - -0.0013182522961869836, - -0.0012626448879018426, - -0.00029802651260979474, - 0.0026779419276863337, - -0.00021618751634377986, - -0.00038464731187559664, - 0 -}; - -void acquire_process(acquire_t *st) -{ - fcomplex_t max_v = CMPLXFSET(0), phase_increment; - float angle, angle_diff, angle_factor, max_mag = -1.0f; - int samperr = 0; - unsigned int i, j, keep; - - if (st->idx != st->fftcp * (ACQUIRE_SYMBOLS + 1)) - return; - - if (st->input->sync_state == SYNC_STATE_FINE) - { - samperr = st->fftcp / 2 + st->input->sync.samperr; - st->input->sync.samperr = 0; - - angle_diff = -st->input->sync.angle; - st->input->sync.angle = 0; - angle = st->prev_angle + angle_diff; - st->prev_angle = angle; - } - else - { - cint16_t y; - for (i = 0; i < st->fftcp * (ACQUIRE_SYMBOLS + 1); i++) - { - fir_q15_execute((st->mode == NRSC5_MODE_FM) ? st->filter_fm : st->filter_am, &st->in_buffer[i], &y); - st->buffer[i] = (st->mode == NRSC5_MODE_FM) ? cq15_to_cf_conj(y) : cq15_to_cf(y); - } - - memset(st->sums, 0, sizeof(fcomplex_t) * st->fftcp); - for (i = 0; i < st->fftcp; ++i) - { - for (j = 0; j < ACQUIRE_SYMBOLS; ++j) - st->sums[i] = CMPLXFADD(st->sums[i], CMPLXFMUL(st->buffer[i + j * st->fftcp], conjf(st->buffer[i + j * st->fftcp + st->fft]))); - } - - for (i = 0; i < st->fftcp; ++i) - { - float mag; - fcomplex_t v = CMPLXFSET(0); - - for (j = 0; j < st->cp; ++j) - v = CMPLXFADD(v, CMPLXFMULF(CMPLXFMULF(st->sums[(i + j) % st->fftcp], st->shape[j]), st->shape[j + st->fft])); - - mag = normf(v); - if (mag > max_mag) - { - max_mag = mag; - max_v = v; - samperr = (i + st->fftcp - FILTER_DELAY) % st->fftcp; - } - } - - angle_diff = cargf(CMPLXFMUL(max_v, cexpf(CMPLXFMULF(I, -st->prev_angle)))); - angle_factor = (st->prev_angle) ? 0.25 : 1.0; - angle = st->prev_angle + (angle_diff * angle_factor); - st->prev_angle = angle; - input_set_sync_state(st->input, SYNC_STATE_COARSE); - } - - for (i = 0; i < st->fftcp * (ACQUIRE_SYMBOLS + 1); i++) - st->buffer[i] = (st->mode == NRSC5_MODE_FM) ? cq15_to_cf_conj(st->in_buffer[i]) : cq15_to_cf(st->in_buffer[i]); - - sync_adjust(&st->input->sync, st->fftcp / 2 - samperr); - angle -= 2 * M_PI * st->cfo; - - st->phase = CMPLXFMUL(st->phase, cexpf(CMPLXFMULF(I, -(st->fftcp / 2 - samperr) * angle / st->fft))); - - phase_increment = cexpf(CMPLXFMULF(I, angle / st->fft)); - - if (st->mode == NRSC5_MODE_AM) - { - float y, sum_y = 0, sum_xy = 0, sum_x2 = 0; - fcomplex_t last_carrier; - fcomplex_t temp_phase = st->phase; - float mag_sums[FFT_AM] = {0}; - - for (i = 0; i < ACQUIRE_SYMBOLS; ++i) - { - int offset = (st->mode == NRSC5_MODE_FM) ? 0 : (FFT_AM - CP_AM) / 2; - for (int j = 0; j < st->fftcp; ++j) - { - fcomplex_t sample = CMPLXFMUL(temp_phase, st->buffer[i * st->fftcp + j + samperr]); - if(j < st->cp) - st->fftin[(j + offset) % st->fft] = CMPLXFMULF(sample, st->shape[j]); - else if(j < st->fft) - st->fftin[(j + offset) % st->fft] = sample; - else - st->fftin[(j + offset) % st->fft] = CMPLXFADD(st->fftin[(j + offset) % st->fft], CMPLXFMULF(sample, st->shape[j])); - - temp_phase = CMPLXFMUL(temp_phase, phase_increment); - } - temp_phase = CMPLXFDIVF(temp_phase, cabsf(temp_phase)); - - fftwf_execute((st->mode == NRSC5_MODE_FM) ? st->fft_plan_fm : st->fft_plan_am); - fftshift(st->fftout, st->fft); - - float x = st->fftcp * (i - (float) (ACQUIRE_SYMBOLS - 1) / 2); - if(i == 0) - y = cargf(st->fftout[CENTER_AM]); - else - y += cargf(CMPLXFDIV(st->fftout[CENTER_AM], last_carrier)); - last_carrier = st->fftout[CENTER_AM]; - - sum_y += y; - sum_xy += x * y; - sum_x2 += x * x; - - if (st->input->sync_state != SYNC_STATE_FINE) - { - for (int j = CENTER_AM - PIDS_2_INDEX_AM; j <= CENTER_AM + PIDS_2_INDEX_AM; j++) - { - mag_sums[j] += cabsf(st->fftout[j]); - } - } - } - - if (st->input->sync_state != SYNC_STATE_FINE) - { - float max_mag = -1.0f; - int max_index = -1; - for (int j = CENTER_AM - PIDS_2_INDEX_AM; j <= CENTER_AM + PIDS_2_INDEX_AM; j++) - { - if (mag_sums[j] > max_mag) - { - max_mag = mag_sums[j]; - max_index = j; - } - } - acquire_cfo_adjust(st, max_index - CENTER_AM); - } - - phase_increment = CMPLXFMUL(phase_increment, cexpf(CMPLXFMULF(I, -sum_xy / sum_x2))); - // TODO: Investigate why 0.06 is needed below - st->phase = CMPLXFMUL(st->phase, cexpf(CMPLXFMULF(I, (-sum_y / ACQUIRE_SYMBOLS + (sum_xy / sum_x2) * (ACQUIRE_SYMBOLS)*st->fftcp / 2 - 0.06)))); - } - - for (i = 0; i < ACQUIRE_SYMBOLS; ++i) - { - int offset = (st->mode == NRSC5_MODE_FM) ? 0 : (FFT_AM - CP_AM) / 2; - for (int j = 0; j < st->fftcp; ++j) - { - fcomplex_t sample = CMPLXFMUL(st->phase, st->buffer[i * st->fftcp + j + samperr]); - if (j < st->cp) - st->fftin[(j + offset) % st->fft] = CMPLXFMULF(sample, st->shape[j]); - else if (j < st->fft) - st->fftin[(j + offset) % st->fft] = sample; - else - st->fftin[(j + offset) % st->fft] = CMPLXFADD(st->fftin[(j + offset) % st->fft], CMPLXFMULF(sample, st->shape[j])); - - st->phase = CMPLXFMUL(st->phase, phase_increment); - } - st->phase = CMPLXFDIVF(st->phase, cabsf(st->phase)); - - fftwf_execute((st->mode == NRSC5_MODE_FM) ? st->fft_plan_fm : st->fft_plan_am); - fftshift(st->fftout, st->fft); - sync_push(&st->input->sync, st->fftout); - } - - keep = st->fftcp + (st->fftcp / 2 - samperr); - memmove(&st->in_buffer[0], &st->in_buffer[st->idx - keep], sizeof(cint16_t) * keep); - st->idx = keep; -} - -void acquire_cfo_adjust(acquire_t *st, int cfo) -{ - float hz; - - if (cfo == 0) - return; - - st->cfo += cfo; - hz = (float) st->cfo * SAMPLE_RATE / st->fft; - hz /= (st->mode == NRSC5_MODE_FM ? DECIMATION_FACTOR_FM : DECIMATION_FACTOR_AM); - - log_info("CFO: %f Hz", hz); -} - -unsigned int acquire_push(acquire_t *st, cint16_t *buf, unsigned int length) -{ - unsigned int needed = st->fftcp - st->idx % st->fftcp; - - if (length < needed) - return 0; - - memcpy(&st->in_buffer[st->idx], buf, sizeof(cint16_t) * needed); - st->idx += needed; - - return needed; -} - -void acquire_reset(acquire_t *st) -{ - firdecim_q15_reset(st->filter_fm); - firdecim_q15_reset(st->filter_am); - st->idx = 0; - st->prev_angle = 0; - st->phase = CMPLXFSET(1); - st->cfo = 0; -} - -void acquire_init(acquire_t *st, input_t *input) -{ - int i; - - st->mode = NRSC5_MODE_FM; - st->fft = FFT_FM; - st->fftcp = FFTCP_FM; - st->cp = CP_FM; - - st->input = input; - - st->filter_fm = firdecim_q15_create(filter_taps_fm, sizeof(filter_taps_fm) / sizeof(filter_taps_fm[0])); - st->filter_am = firdecim_q15_create(filter_taps_am, sizeof(filter_taps_am) / sizeof(filter_taps_am[0])); - - st->fft_plan_fm = fftwf_plan_dft_1d(FFT_FM, (fftwf_complex*)st->fftin, (fftwf_complex*)st->fftout, FFTW_FORWARD, 0); - st->fft_plan_am = fftwf_plan_dft_1d(FFT_AM, (fftwf_complex*)st->fftin, (fftwf_complex*)st->fftout, FFTW_FORWARD, 0); - - for (i = 0; i < FFTCP_FM; ++i) - { - // Pulse shaping window function for FM - if (i < CP_FM) - st->shape_fm[i] = sinf(M_PI / 2 * i / CP_FM); - else if (i < FFT_FM) - st->shape_fm[i] = 1; - else - st->shape_fm[i] = cosf(M_PI / 2 * (i - FFT_FM) / CP_FM); - } - - for (i = 0; i < FFTCP_AM; ++i) - { - // Pulse shaping window function for AM - if (i < CP_AM) - st->shape_am[i] = sinf(M_PI / 2 * i / CP_AM); - else if (i < FFT_AM) - st->shape_am[i] = 1; - else - st->shape_am[i] = cosf(M_PI / 2 * (i - FFT_AM) / CP_AM); - } - - st->shape = st->shape_fm; - - acquire_reset(st); -} - -void acquire_set_mode(acquire_t *st, int mode) -{ - st->mode = mode; - - if (st->mode == NRSC5_MODE_FM) - { - st->fft = FFT_FM; - st->fftcp = FFTCP_FM; - st->cp = CP_FM; - st->shape = st->shape_fm; - } - else - { - st->fft = FFT_AM; - st->fftcp = FFTCP_AM; - st->cp = CP_AM; - st->shape = st->shape_am; - } -} - -void acquire_free(acquire_t *st) -{ - firdecim_q15_free(st->filter_fm); - firdecim_q15_free(st->filter_am); - fftwf_destroy_plan(st->fft_plan_fm); - fftwf_destroy_plan(st->fft_plan_am); -} +/* + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifdef _MSC_VER +#define _USE_MATH_DEFINES +#endif + +#include +#include + +#include "acquire.h" +#include "defines.h" +#include "input.h" + +#define FILTER_DELAY 15 +#define DECIMATION_FACTOR_FM 2 +#define DECIMATION_FACTOR_AM 32 + +static float filter_taps_fm[] = { + -0.000685643230099231, + 0.005636964458972216, + 0.009015781804919243, + -0.015486305579543114, + -0.035108357667922974, + 0.017446253448724747, + 0.08155813068151474, + 0.007995186373591423, + -0.13311293721199036, + -0.0727422907948494, + 0.15914097428321838, + 0.16498781740665436, + -0.1324498951435089, + -0.2484012246131897, + 0.051773931831121445, + 0.2821577787399292, + 0.051773931831121445, + -0.2484012246131897, + -0.1324498951435089, + 0.16498781740665436, + 0.15914097428321838, + -0.0727422907948494, + -0.13311293721199036, + 0.007995186373591423, + 0.08155813068151474, + 0.017446253448724747, + -0.035108357667922974, + -0.015486305579543114, + 0.009015781804919243, + 0.005636964458972216, + -0.000685643230099231, + 0 +}; + +static float filter_taps_am[] = { + -0.00038464731187559664, + -0.00021618751634377986, + 0.0026779419276863337, + -0.00029802651260979474, + -0.0012626448879018426, + -0.0013182522961869836, + -0.012252614833414555, + 0.015980124473571777, + 0.037112727761268616, + -0.05451361835002899, + -0.05804193392395973, + 0.11320608854293823, + 0.055298302322626114, + -0.16878043115139008, + -0.022917453199625015, + 0.19178225100040436, + -0.022917453199625015, + -0.16878043115139008, + 0.055298302322626114, + 0.11320608854293823, + -0.05804193392395973, + -0.05451361835002899, + 0.037112727761268616, + 0.015980124473571777, + -0.012252614833414555, + -0.0013182522961869836, + -0.0012626448879018426, + -0.00029802651260979474, + 0.0026779419276863337, + -0.00021618751634377986, + -0.00038464731187559664, + 0 +}; + +void acquire_process(acquire_t *st) +{ + fcomplex_t max_v = CMPLXFSET(0), phase_increment; + float angle, angle_diff, angle_factor, max_mag = -1.0f; + int samperr = 0; + unsigned int i, j, keep; + + if (st->idx != st->fftcp * (ACQUIRE_SYMBOLS + 1)) + return; + + if (st->input->sync_state == SYNC_STATE_FINE) + { + samperr = st->fftcp / 2 + st->input->sync.samperr; + st->input->sync.samperr = 0; + + angle_diff = -st->input->sync.angle; + st->input->sync.angle = 0; + angle = st->prev_angle + angle_diff; + st->prev_angle = angle; + } + else + { + cint16_t y; + for (i = 0; i < st->fftcp * (ACQUIRE_SYMBOLS + 1); i++) + { + fir_q15_execute((st->mode == NRSC5_MODE_FM) ? st->filter_fm : st->filter_am, &st->in_buffer[i], &y); + st->buffer[i] = (st->mode == NRSC5_MODE_FM) ? cq15_to_cf_conj(y) : cq15_to_cf(y); + } + + memset(st->sums, 0, sizeof(fcomplex_t) * st->fftcp); + for (i = 0; i < st->fftcp; ++i) + { + for (j = 0; j < ACQUIRE_SYMBOLS; ++j) + st->sums[i] = CMPLXFADD(st->sums[i], CMPLXFMUL(st->buffer[i + j * st->fftcp], conjf(st->buffer[i + j * st->fftcp + st->fft]))); + } + + for (i = 0; i < st->fftcp; ++i) + { + float mag; + fcomplex_t v = CMPLXFSET(0); + + for (j = 0; j < st->cp; ++j) + v = CMPLXFADD(v, CMPLXFMULF(CMPLXFMULF(st->sums[(i + j) % st->fftcp], st->shape[j]), st->shape[j + st->fft])); + + mag = normf(v); + if (mag > max_mag) + { + max_mag = mag; + max_v = v; + samperr = (i + st->fftcp - FILTER_DELAY) % st->fftcp; + } + } + + angle_diff = cargf(CMPLXFMUL(max_v, cexpf(CMPLXFMULF(I, -st->prev_angle)))); + angle_factor = (st->prev_angle) ? 0.25 : 1.0; + angle = st->prev_angle + (angle_diff * angle_factor); + st->prev_angle = angle; + input_set_sync_state(st->input, SYNC_STATE_COARSE); + } + + for (i = 0; i < st->fftcp * (ACQUIRE_SYMBOLS + 1); i++) + st->buffer[i] = (st->mode == NRSC5_MODE_FM) ? cq15_to_cf_conj(st->in_buffer[i]) : cq15_to_cf(st->in_buffer[i]); + + sync_adjust(&st->input->sync, st->fftcp / 2 - samperr); + angle -= 2 * M_PI * st->cfo; + + st->phase = CMPLXFMUL(st->phase, cexpf(CMPLXFMULF(I, -(st->fftcp / 2 - samperr) * angle / st->fft))); + + phase_increment = cexpf(CMPLXFMULF(I, angle / st->fft)); + + if (st->mode == NRSC5_MODE_AM) + { + float y, sum_y = 0, sum_xy = 0, sum_x2 = 0; + fcomplex_t last_carrier; + fcomplex_t temp_phase = st->phase; + float mag_sums[FFT_AM] = {0}; + + for (i = 0; i < ACQUIRE_SYMBOLS; ++i) + { + int offset = (st->mode == NRSC5_MODE_FM) ? 0 : (FFT_AM - CP_AM) / 2; + for (int j = 0; j < st->fftcp; ++j) + { + fcomplex_t sample = CMPLXFMUL(temp_phase, st->buffer[i * st->fftcp + j + samperr]); + if(j < st->cp) + st->fftin[(j + offset) % st->fft] = CMPLXFMULF(sample, st->shape[j]); + else if(j < st->fft) + st->fftin[(j + offset) % st->fft] = sample; + else + st->fftin[(j + offset) % st->fft] = CMPLXFADD(st->fftin[(j + offset) % st->fft], CMPLXFMULF(sample, st->shape[j])); + + temp_phase = CMPLXFMUL(temp_phase, phase_increment); + } + temp_phase = CMPLXFDIVF(temp_phase, cabsf(temp_phase)); + + fftwf_execute((st->mode == NRSC5_MODE_FM) ? st->fft_plan_fm : st->fft_plan_am); + fftshift(st->fftout, st->fft); + + float x = st->fftcp * (i - (float) (ACQUIRE_SYMBOLS - 1) / 2); + if(i == 0) + y = cargf(st->fftout[CENTER_AM]); + else + y += cargf(CMPLXFDIV(st->fftout[CENTER_AM], last_carrier)); + last_carrier = st->fftout[CENTER_AM]; + + sum_y += y; + sum_xy += x * y; + sum_x2 += x * x; + + if (st->input->sync_state != SYNC_STATE_FINE) + { + for (int j = CENTER_AM - PIDS_2_INDEX_AM; j <= CENTER_AM + PIDS_2_INDEX_AM; j++) + { + mag_sums[j] += cabsf(st->fftout[j]); + } + } + } + + if (st->input->sync_state != SYNC_STATE_FINE) + { + float max_mag = -1.0f; + int max_index = -1; + for (int j = CENTER_AM - PIDS_2_INDEX_AM; j <= CENTER_AM + PIDS_2_INDEX_AM; j++) + { + if (mag_sums[j] > max_mag) + { + max_mag = mag_sums[j]; + max_index = j; + } + } + acquire_cfo_adjust(st, max_index - CENTER_AM); + } + + phase_increment = CMPLXFMUL(phase_increment, cexpf(CMPLXFMULF(I, -sum_xy / sum_x2))); + // TODO: Investigate why 0.06 is needed below + st->phase = CMPLXFMUL(st->phase, cexpf(CMPLXFMULF(I, (-sum_y / ACQUIRE_SYMBOLS + (sum_xy / sum_x2) * (ACQUIRE_SYMBOLS)*st->fftcp / 2 - 0.06)))); + } + + for (i = 0; i < ACQUIRE_SYMBOLS; ++i) + { + int offset = (st->mode == NRSC5_MODE_FM) ? 0 : (FFT_AM - CP_AM) / 2; + for (int j = 0; j < st->fftcp; ++j) + { + fcomplex_t sample = CMPLXFMUL(st->phase, st->buffer[i * st->fftcp + j + samperr]); + if (j < st->cp) + st->fftin[(j + offset) % st->fft] = CMPLXFMULF(sample, st->shape[j]); + else if (j < st->fft) + st->fftin[(j + offset) % st->fft] = sample; + else + st->fftin[(j + offset) % st->fft] = CMPLXFADD(st->fftin[(j + offset) % st->fft], CMPLXFMULF(sample, st->shape[j])); + + st->phase = CMPLXFMUL(st->phase, phase_increment); + } + st->phase = CMPLXFDIVF(st->phase, cabsf(st->phase)); + + fftwf_execute((st->mode == NRSC5_MODE_FM) ? st->fft_plan_fm : st->fft_plan_am); + fftshift(st->fftout, st->fft); + sync_push(&st->input->sync, st->fftout); + } + + keep = st->fftcp + (st->fftcp / 2 - samperr); + memmove(&st->in_buffer[0], &st->in_buffer[st->idx - keep], sizeof(cint16_t) * keep); + st->idx = keep; +} + +void acquire_cfo_adjust(acquire_t *st, int cfo) +{ + float hz; + + if (cfo == 0) + return; + + st->cfo += cfo; + hz = (float) st->cfo * SAMPLE_RATE / st->fft; + hz /= (st->mode == NRSC5_MODE_FM ? DECIMATION_FACTOR_FM : DECIMATION_FACTOR_AM); + + log_info("CFO: %f Hz", hz); +} + +unsigned int acquire_push(acquire_t *st, cint16_t *buf, unsigned int length) +{ + unsigned int needed = st->fftcp - st->idx % st->fftcp; + + if (length < needed) + return 0; + + memcpy(&st->in_buffer[st->idx], buf, sizeof(cint16_t) * needed); + st->idx += needed; + + return needed; +} + +void acquire_reset(acquire_t *st) +{ + firdecim_q15_reset(st->filter_fm); + firdecim_q15_reset(st->filter_am); + st->idx = 0; + st->prev_angle = 0; + st->phase = CMPLXFSET(1); + st->cfo = 0; +} + +void acquire_init(acquire_t *st, input_t *input) +{ + int i; + + st->mode = NRSC5_MODE_FM; + st->fft = FFT_FM; + st->fftcp = FFTCP_FM; + st->cp = CP_FM; + + st->input = input; + + st->filter_fm = firdecim_q15_create(filter_taps_fm, sizeof(filter_taps_fm) / sizeof(filter_taps_fm[0])); + st->filter_am = firdecim_q15_create(filter_taps_am, sizeof(filter_taps_am) / sizeof(filter_taps_am[0])); + + st->fft_plan_fm = fftwf_plan_dft_1d(FFT_FM, (fftwf_complex*)st->fftin, (fftwf_complex*)st->fftout, FFTW_FORWARD, 0); + st->fft_plan_am = fftwf_plan_dft_1d(FFT_AM, (fftwf_complex*)st->fftin, (fftwf_complex*)st->fftout, FFTW_FORWARD, 0); + + for (i = 0; i < FFTCP_FM; ++i) + { + // Pulse shaping window function for FM + if (i < CP_FM) + st->shape_fm[i] = sinf(M_PI / 2 * i / CP_FM); + else if (i < FFT_FM) + st->shape_fm[i] = 1; + else + st->shape_fm[i] = cosf(M_PI / 2 * (i - FFT_FM) / CP_FM); + } + + for (i = 0; i < FFTCP_AM; ++i) + { + // Pulse shaping window function for AM + if (i < CP_AM) + st->shape_am[i] = sinf(M_PI / 2 * i / CP_AM); + else if (i < FFT_AM) + st->shape_am[i] = 1; + else + st->shape_am[i] = cosf(M_PI / 2 * (i - FFT_AM) / CP_AM); + } + + st->shape = st->shape_fm; + + acquire_reset(st); +} + +void acquire_set_mode(acquire_t *st, int mode) +{ + st->mode = mode; + + if (st->mode == NRSC5_MODE_FM) + { + st->fft = FFT_FM; + st->fftcp = FFTCP_FM; + st->cp = CP_FM; + st->shape = st->shape_fm; + } + else + { + st->fft = FFT_AM; + st->fftcp = FFTCP_AM; + st->cp = CP_AM; + st->shape = st->shape_am; + } +} + +void acquire_free(acquire_t *st) +{ + firdecim_q15_free(st->filter_fm); + firdecim_q15_free(st->filter_am); + fftwf_destroy_plan(st->fft_plan_fm); + fftwf_destroy_plan(st->fft_plan_am); +} diff --git a/src/hddsp/acquire.h b/src/hddsp/acquire.h index 35585ca..f97c87a 100644 --- a/src/hddsp/acquire.h +++ b/src/hddsp/acquire.h @@ -1,41 +1,41 @@ -#pragma once - -#define __ STDC_NO_COMPLEX __ -#include -#include -#include "firdecim_q15.h" - -typedef struct -{ - struct input_t *input; - firdecim_q15 filter_fm; - firdecim_q15 filter_am; - cint16_t in_buffer[FFTCP_FM * (ACQUIRE_SYMBOLS + 1)]; - fcomplex_t buffer[FFTCP_FM * (ACQUIRE_SYMBOLS + 1)]; - fcomplex_t sums[FFTCP_FM]; - fcomplex_t fftin[FFT_FM]; - fcomplex_t fftout[FFT_FM]; - float *shape; - float shape_fm[FFTCP_FM]; - float shape_am[FFTCP_AM]; - fftwf_plan fft_plan_fm; - fftwf_plan fft_plan_am; - - unsigned int idx; - float prev_angle; - fcomplex_t phase; - int cfo; - - int mode; - int fft; - int fftcp; - int cp; -} acquire_t; - -void acquire_process(acquire_t *st); -void acquire_cfo_adjust(acquire_t *st, int cfo); -unsigned int acquire_push(acquire_t *st, cint16_t *buf, unsigned int length); -void acquire_reset(acquire_t *st); -void acquire_init(acquire_t *st, struct input_t *input); -void acquire_set_mode(acquire_t *st, int mode); -void acquire_free(acquire_t *st); +#pragma once + +#define __ STDC_NO_COMPLEX __ +#include +#include +#include "firdecim_q15.h" + +typedef struct +{ + struct input_t *input; + firdecim_q15 filter_fm; + firdecim_q15 filter_am; + cint16_t in_buffer[FFTCP_FM * (ACQUIRE_SYMBOLS + 1)]; + fcomplex_t buffer[FFTCP_FM * (ACQUIRE_SYMBOLS + 1)]; + fcomplex_t sums[FFTCP_FM]; + fcomplex_t fftin[FFT_FM]; + fcomplex_t fftout[FFT_FM]; + float *shape; + float shape_fm[FFTCP_FM]; + float shape_am[FFTCP_AM]; + fftwf_plan fft_plan_fm; + fftwf_plan fft_plan_am; + + unsigned int idx; + float prev_angle; + fcomplex_t phase; + int cfo; + + int mode; + int fft; + int fftcp; + int cp; +} acquire_t; + +void acquire_process(acquire_t *st); +void acquire_cfo_adjust(acquire_t *st, int cfo); +unsigned int acquire_push(acquire_t *st, cint16_t *buf, unsigned int length); +void acquire_reset(acquire_t *st); +void acquire_init(acquire_t *st, struct input_t *input); +void acquire_set_mode(acquire_t *st, int mode); +void acquire_free(acquire_t *st); diff --git a/src/hddsp/bitwriter.h b/src/hddsp/bitwriter.h index ffa4e37..7ac5de4 100644 --- a/src/hddsp/bitwriter.h +++ b/src/hddsp/bitwriter.h @@ -1,43 +1,43 @@ -#pragma once - -#include - -typedef struct -{ - unsigned int byte; - unsigned int bits; - uint8_t *buf, *begin; -} bitwriter_t; - -static inline void bw_init(bitwriter_t *bw, uint8_t *buf) -{ - bw->byte = 0; - bw->bits = 0; - bw->buf = buf; - bw->begin = buf; -} - -static inline void bw_add1bit(bitwriter_t *bw, unsigned int bit) -{ - bw->byte = (bw->byte << 1) | (!!bit); - if (++bw->bits == 8) - { - *bw->buf++ = bw->byte; - bw->byte = 0; - bw->bits = 0; - } -} - -static inline void bw_addbits(bitwriter_t *bw, unsigned int value, unsigned int bits) -{ - unsigned int i; - for (i = 0; i < bits; ++i) - bw_add1bit(bw, value & (1 << (bits - i - 1))); -} - -static inline unsigned int bw_flush(bitwriter_t *bw) -{ - if (bw->bits) - bw_addbits(bw, 0, 8 - bw->bits); - return bw->buf - bw->begin; -} +#pragma once + +#include + +typedef struct +{ + unsigned int byte; + unsigned int bits; + uint8_t *buf, *begin; +} bitwriter_t; + +static inline void bw_init(bitwriter_t *bw, uint8_t *buf) +{ + bw->byte = 0; + bw->bits = 0; + bw->buf = buf; + bw->begin = buf; +} + +static inline void bw_add1bit(bitwriter_t *bw, unsigned int bit) +{ + bw->byte = (bw->byte << 1) | (!!bit); + if (++bw->bits == 8) + { + *bw->buf++ = bw->byte; + bw->byte = 0; + bw->bits = 0; + } +} + +static inline void bw_addbits(bitwriter_t *bw, unsigned int value, unsigned int bits) +{ + unsigned int i; + for (i = 0; i < bits; ++i) + bw_add1bit(bw, value & (1 << (bits - i - 1))); +} + +static inline unsigned int bw_flush(bitwriter_t *bw) +{ + if (bw->bits) + bw_addbits(bw, 0, 8 - bw->bits); + return bw->buf - bw->begin; +} diff --git a/src/hddsp/config.h b/src/hddsp/config.h index 0126f24..8ad1c30 100644 --- a/src/hddsp/config.h +++ b/src/hddsp/config.h @@ -1,82 +1,82 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//----------------------------------------------------------------------------- - -#ifndef __NRSC5_CONFIG_H_ -#define __NRSC5_CONFIG_H_ -#pragma once - -#define LIBRARY_DEBUG_LEVEL 5 - -// Windows -#if defined(_WINDOWS) - -#define HAVE_COMPLEX_I -//#define HAVE_FAAD2 -#define strdup _strdup - -// Android -#elif defined(__ANDROID__) - -#define HAVE_STRNDUP -#define HAVE_CMPLXF -#define HAVE_COMPLEX_I - -#if(__ANDROID_API__ < 23) -// src/compat/bionic/complex.cpp -float cabsf(float _Complex __z); -float cargf(float _Complex __z); -float _Complex cexpf(float _Complex __z); -float cimagf(float _Complex __z); -float _Complex conjf(float _Complex __z); -float crealf(float _Complex __z); -#endif - -// MacOS -#elif defined(__APPLE__) - -#define HAVE_PTHREAD_SETNAME_NP -#define HAVE_STRNDUP -#define HAVE_CMPLXF -#define HAVE_COMPLEX_I -//#define HAVE_FAAD2 - -// Linux -#else - -#define HAVE_PTHREAD_SETNAME_NP -#define HAVE_STRNDUP -#define HAVE_CMPLXF -#define HAVE_COMPLEX_I -//#define HAVE_FAAD2 - -#endif - -#ifdef HAVE_FAAD2 -#define USE_FAAD2 -#endif - -#ifndef HAVE_STRNDUP -#include -char* strndup(char const* s, size_t n); -#endif - -#endif // __NRSC5_CONFIG_H_ +//----------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef __NRSC5_CONFIG_H_ +#define __NRSC5_CONFIG_H_ +#pragma once + +#define LIBRARY_DEBUG_LEVEL 5 + +// Windows +#if defined(_WINDOWS) + +#define HAVE_COMPLEX_I +//#define HAVE_FAAD2 +#define strdup _strdup + +// Android +#elif defined(__ANDROID__) + +#define HAVE_STRNDUP +#define HAVE_CMPLXF +#define HAVE_COMPLEX_I + +#if(__ANDROID_API__ < 23) +// src/compat/bionic/complex.cpp +float cabsf(float _Complex __z); +float cargf(float _Complex __z); +float _Complex cexpf(float _Complex __z); +float cimagf(float _Complex __z); +float _Complex conjf(float _Complex __z); +float crealf(float _Complex __z); +#endif + +// MacOS +#elif defined(__APPLE__) + +#define HAVE_PTHREAD_SETNAME_NP +#define HAVE_STRNDUP +#define HAVE_CMPLXF +#define HAVE_COMPLEX_I +//#define HAVE_FAAD2 + +// Linux +#else + +#define HAVE_PTHREAD_SETNAME_NP +#define HAVE_STRNDUP +#define HAVE_CMPLXF +#define HAVE_COMPLEX_I +//#define HAVE_FAAD2 + +#endif + +#ifdef HAVE_FAAD2 +#define USE_FAAD2 +#endif + +#ifndef HAVE_STRNDUP +#include +char* strndup(char const* s, size_t n); +#endif + +#endif // __NRSC5_CONFIG_H_ diff --git a/src/hddsp/conv.h b/src/hddsp/conv.h index bc4a971..decdda2 100644 --- a/src/hddsp/conv.h +++ b/src/hddsp/conv.h @@ -1,38 +1,38 @@ -#ifndef _CONV_H_ -#define _CONV_H_ - -#include - -enum { - CONV_TERM_FLUSH, - CONV_TERM_TAIL_BITING, -}; - -/* - * Convolutional code descriptor - * - * n - Rate 2, 3, 4 (1/2, 1/3, 1/4) - * k - Constraint length (5 or 7) - * rgen - Recursive generator polynomial in octal - * gen - Generator polynomials in octal - * punc - Puncturing matrix (-1 terminated) - * term - Termination type (zero flush default) - */ -struct lte_conv_code { - int n; - int k; - int len; - unsigned rgen; - unsigned gen[4]; - int *punc; - int term; -}; - -int nrsc5_conv_decode_p1(const int8_t *in, uint8_t *out); -int nrsc5_conv_decode_pids(const int8_t *in, uint8_t *out); -int nrsc5_conv_decode_p3(const int8_t *in, uint8_t *out); -int nrsc5_conv_decode_e1(const int8_t *in, uint8_t *out, int len); -int nrsc5_conv_decode_e2(const int8_t *in, uint8_t *out, int len); -int nrsc5_conv_decode_e3(const int8_t *in, uint8_t *out, int len); - -#endif /* _CONV_H_ */ +#ifndef _CONV_H_ +#define _CONV_H_ + +#include + +enum { + CONV_TERM_FLUSH, + CONV_TERM_TAIL_BITING, +}; + +/* + * Convolutional code descriptor + * + * n - Rate 2, 3, 4 (1/2, 1/3, 1/4) + * k - Constraint length (5 or 7) + * rgen - Recursive generator polynomial in octal + * gen - Generator polynomials in octal + * punc - Puncturing matrix (-1 terminated) + * term - Termination type (zero flush default) + */ +struct lte_conv_code { + int n; + int k; + int len; + unsigned rgen; + unsigned gen[4]; + int *punc; + int term; +}; + +int nrsc5_conv_decode_p1(const int8_t *in, uint8_t *out); +int nrsc5_conv_decode_pids(const int8_t *in, uint8_t *out); +int nrsc5_conv_decode_p3(const int8_t *in, uint8_t *out); +int nrsc5_conv_decode_e1(const int8_t *in, uint8_t *out, int len); +int nrsc5_conv_decode_e2(const int8_t *in, uint8_t *out, int len); +int nrsc5_conv_decode_e3(const int8_t *in, uint8_t *out, int len); + +#endif /* _CONV_H_ */ diff --git a/src/hddsp/conv_dec.c b/src/hddsp/conv_dec.c index 28716f5..04cd408 100644 --- a/src/hddsp/conv_dec.c +++ b/src/hddsp/conv_dec.c @@ -1,482 +1,482 @@ -/* - * Viterbi decoder for convolutional codes - * - * Copyright (C) 2015 Ettus Research LLC - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Author: Tom Tsou - */ - -#include "config.h" - -#include -#ifndef __APPLE__ -#include -#endif -#include -#include -#include - -#include "defines.h" -#include "conv.h" - -#include "conv_gen.h" -#if defined(HAVE_SSE3) -#include "conv_sse.h" -#elif defined(HAVE_NEON) -#include "conv_neon.h" -#endif - -#define TAIL_BITING_EXTRA 32 - -/* - * Trellis State - * - * state - Internal shift register value - * prev - Register values of previous 0 and 1 states - */ -struct vstate { - unsigned state; - unsigned prev[2]; -}; - -/* - * Trellis Object - * - * num_states - Number of states in the trellis - * sums - Accumulated path metrics - * outputs - Trellis ouput values - * vals - Input value that led to each state - */ -struct vtrellis { - int num_states; - int16_t *sums; - int16_t *outputs; - uint8_t *vals; -}; - -/* - * Viterbi Decoder - * - * n - Code order - * k - Constraint length - * len - Horizontal length of trellis - * recursive - Set to '1' if the code is recursive - * intrvl - Normalization interval - * trellis - Trellis object - * punc - Puncturing sequence - * paths - Trellis paths - */ -struct vdecoder { - int n; - int k; - int len; - int recursive; - int intrvl; - struct vtrellis *trellis; - int *punc; - int16_t **paths; - - void (*metric_func)(const int8_t *, const int16_t *, - int16_t *, int16_t *, int); -}; - -/* - * Aligned Memory Allocator - * - * SSE requires 16-byte memory alignment. We store relevant trellis values - * (accumulated sums, outputs, and path decisions) as 16 bit signed integers - * so the allocated memory is casted as such. - */ -#define SSE_ALIGN 16 - -static int16_t *vdec_malloc(size_t n) -{ -#if defined(HAVE_SSE3) && !defined(__APPLE__) - return (int16_t *) memalign(SSE_ALIGN, sizeof(int16_t) * n); -#else - return (int16_t *) malloc(sizeof(int16_t) * n); -#endif -} - -/* Left shift and mask for finding the previous state */ -static unsigned vstate_lshift(unsigned reg, int k, int val) -{ - unsigned mask; - - if (k == 5) - mask = 0x0e; - else if (k == 7) - mask = 0x3e; - else if (k == 9) - mask = 0xfe; - else - mask = 0; - - return ((reg << 1) & mask) | val; -} - -/* - * Populate non-recursive trellis state - * - * For a given state defined by the k-1 length shift register, find the - * value of the input bit that drove the trellis to that state. Then - * generate the N outputs of the generator polynomial at that state. - */ -static void gen_state_info(const struct lte_conv_code *code, - uint8_t *val, unsigned reg, int16_t *out) -{ - int i; - unsigned prev; - - /* Previous '0' state */ - prev = vstate_lshift(reg, code->k, 0); - - /* Compute output and unpack to NRZ */ - *val = (reg >> (code->k - 2)) & 0x01; - prev = prev | (unsigned) *val << (code->k - 1); - - for (i = 0; i < code->n; i++) - out[i] = PARITY(prev & code->gen[i]) * 2 - 1; -} - -/* - * Populate recursive trellis state - */ -static void gen_rec_state_info(const struct lte_conv_code *code, - uint8_t *val, unsigned reg, int16_t *out) -{ - int i; - unsigned prev, rec, mask; - - /* Previous '0' and '1' states */ - prev = vstate_lshift(reg, code->k, 0); - - /* Compute recursive input value (not the value shifted into register) */ - rec = (reg >> (code->k - 2)) & 0x01; - - if ((unsigned int) PARITY(prev & code->rgen) == rec) - *val = 0; - else - *val = 1; - - /* Compute outputs and unpack to NRZ */ - prev = prev | rec << (code->k - 1); - - if (code->k == 5) - mask = 0x0f; - else if (code->k == 7) - mask = 0x3f; - else - mask = 0xff; - - /* Check for recursive outputs */ - for (i = 0; i < code->n; i++) { - if (code->gen[i] & mask) - out[i] = PARITY(prev & code->gen[i]) * 2 - 1; - else - out[i] = *val * 2 - 1; - } -} - -/* Release the trellis */ -static void free_trellis(struct vtrellis *trellis) -{ - if (!trellis) - return; - - free(trellis->vals); - free(trellis->outputs); - free(trellis->sums); - free(trellis); -} - -#define NUM_STATES(K) (K == 9 ? 256 : (K == 7 ? 64 : 16)) - -/* - * Allocate and initialize the trellis object - * - * Initialization consists of generating the outputs and output value of a - * given state. Due to trellis symmetry, only one of the transition paths - * is used by the butterfly operation in the forward recursion, so only one - * set of N outputs is required per state variable. - */ -static struct vtrellis *generate_trellis(const struct lte_conv_code *code) -{ - int i; - struct vtrellis *trellis; - int16_t *out; - - int ns = NUM_STATES(code->k); - int olen = (code->n == 2) ? 2 : 4; - - trellis = (struct vtrellis *) calloc(1, sizeof(struct vtrellis)); - trellis->num_states = ns; - trellis->sums = vdec_malloc(ns); - trellis->outputs = vdec_malloc(ns * olen); - trellis->vals = (uint8_t *) malloc(ns * sizeof(uint8_t)); - - if (!trellis->sums || !trellis->outputs) - goto fail; - - /* Populate the trellis state objects */ - for (i = 0; i < ns; i++) { - out = &trellis->outputs[olen * i]; - - if (code->rgen) - gen_rec_state_info(code, &trellis->vals[i], i, out); - else - gen_state_info(code, &trellis->vals[i], i, out); - } - - return trellis; -fail: - free_trellis(trellis); - return NULL; -} - -/* - * Reset decoder - * - * Set accumulated path metrics to zero. For termination other than - * tail-biting, initialize the zero state as the encoder starting state. - * Intialize with the maximum accumulated sum at length equal to the - * constraint length. - */ -static void reset_decoder(struct vdecoder *dec, int term) -{ - int ns = dec->trellis->num_states; - - memset(dec->trellis->sums, 0, sizeof(int16_t) * ns); - - if (term != CONV_TERM_TAIL_BITING) - dec->trellis->sums[0] = INT8_MAX * dec->n * dec->k; -} - -static int _traceback(struct vdecoder *dec, - unsigned state, uint8_t *out, int len, int offset) -{ - int i; - unsigned path; - - for (i = len - 1; i >= 0; i--) { - path = dec->paths[i + offset][state] + 1; - out[i] = dec->trellis->vals[state]; - state = vstate_lshift(state, dec->k, path); - } - - return state; -} - -static void _traceback_rec(struct vdecoder *dec, - unsigned state, uint8_t *out, int len) -{ - int i; - unsigned path; - - for (i = len - 1; i >= 0; i--) { - path = dec->paths[i][state] + 1; - out[i] = path ^ dec->trellis->vals[state]; - state = vstate_lshift(state, dec->k, path); - } -} - -/* - * Traceback and generate decoded output - * - * For tail biting, find the largest accumulated path metric at the final state - * followed by two trace back passes. For zero flushing the final state is - * always zero with a single traceback path. - */ -static int traceback(struct vdecoder *dec, uint8_t *out, int term, int len) -{ - int i, sum, max_p = -1, max = -1; - unsigned path, state = 0; - - if (term == CONV_TERM_TAIL_BITING) { - for (i = 0; i < dec->trellis->num_states; i++) { - sum = dec->trellis->sums[i]; - if (sum > max) { - max_p = max; - max = sum; - state = i; - } - } - if (max < 0) - return -EPROTO; - for (i = dec->len - 1; i >= len + TAIL_BITING_EXTRA; i--) { - path = dec->paths[i][state] + 1; - state = vstate_lshift(state, dec->k, path); - } - } else { - for (i = dec->len - 1; i >= len; i--) { - path = dec->paths[i][state] + 1; - state = vstate_lshift(state, dec->k, path); - } - } - - if (dec->recursive) - _traceback_rec(dec, state, out, len); - else - state =_traceback(dec, state, out, len, term == CONV_TERM_TAIL_BITING ? TAIL_BITING_EXTRA : 0); - - /* Don't handle the odd case of recursize tail-biting codes */ - - return max - max_p; -} - -/* Release decoder object */ -static void free_vdec(struct vdecoder *dec) -{ - if (!dec) - return; - - free(dec->paths[0]); - free(dec->paths); - free_trellis(dec->trellis); - free(dec); -} - -/* - * Allocate decoder object - * - * Subtract the constraint length K on the normalization interval to - * accommodate the initialization path metric at state zero. - */ -static struct vdecoder *alloc_vdec(const struct lte_conv_code *code) -{ - int i, ns; - struct vdecoder *dec; - - ns = NUM_STATES(code->k); - - dec = (struct vdecoder *) calloc(1, sizeof(struct vdecoder)); - dec->n = code->n; - dec->k = code->k; - dec->recursive = code->rgen ? 1 : 0; - dec->intrvl = INT16_MAX / (dec->n * INT8_MAX) - dec->k; - - assert(dec->n == 3); - assert(dec->k == 7 || dec->k == 9); - - if (code->term == CONV_TERM_FLUSH) - dec->len = code->len + code->k - 1; - else - dec->len = code->len + TAIL_BITING_EXTRA * 2; - - dec->trellis = generate_trellis(code); - if (!dec->trellis) - goto fail; - - dec->paths = (int16_t **) malloc(sizeof(int16_t *) * dec->len); - dec->paths[0] = vdec_malloc(ns * dec->len); - for (i = 1; i < dec->len; i++) - dec->paths[i] = &dec->paths[0][i * ns]; - - return dec; -fail: - free_vdec(dec); - return NULL; -} - -/* - * Forward trellis recursion - * - * Generate branch metrics and path metrics with a combined function. Only - * accumulated path metric sums and path selections are stored. Normalize on - * the interval specified by the decoder. - */ -static void _conv_decode(struct vdecoder *dec, const int8_t *seq, int term, int len) -{ - int i, j = 0; - struct vtrellis *trellis = dec->trellis; - - if (term == CONV_TERM_TAIL_BITING) - j = len - TAIL_BITING_EXTRA; - - for (i = 0; i < dec->len; i++, j++) { - if (term == CONV_TERM_TAIL_BITING && j == len) - j = 0; - - if (dec->k == 7) - gen_metrics_k7_n3(&seq[dec->n * j], - trellis->outputs, - trellis->sums, - dec->paths[i], - !(i % dec->intrvl)); - else if (dec->k == 9) - gen_metrics_k9_n3(&seq[dec->n * j], - trellis->outputs, - trellis->sums, - dec->paths[i], - !(i % dec->intrvl)); - } -} - -static int nrsc5_conv_decode(const int8_t *in, uint8_t *out, int k, int len, unsigned int g1, unsigned int g2, unsigned int g3) -{ - const struct lte_conv_code code = { - .n = 3, - .k = k, - .len = len, - .gen = { g1, g2, g3 }, - .term = CONV_TERM_TAIL_BITING, - }; - int rc; - - struct vdecoder *vdec = alloc_vdec(&code); - if (!vdec) - return -EFAULT; - - reset_decoder(vdec, code.term); - - /* Propagate through the trellis with interval normalization */ - _conv_decode(vdec, in, code.term, code.len); - - rc = traceback(vdec, out, code.term, code.len); - - free_vdec(vdec); - return rc; -} - -int nrsc5_conv_decode_p1(const int8_t *in, uint8_t *out) -{ - return nrsc5_conv_decode(in, out, 7, P1_FRAME_LEN_FM, 0133, 0171, 0165); -} - -int nrsc5_conv_decode_pids(const int8_t *in, uint8_t *out) -{ - return nrsc5_conv_decode(in, out, 7, PIDS_FRAME_LEN, 0133, 0171, 0165); -} - -int nrsc5_conv_decode_p3(const int8_t *in, uint8_t *out) -{ - return nrsc5_conv_decode(in, out, 7, P3_FRAME_LEN_FM, 0133, 0171, 0165); -} - -int nrsc5_conv_decode_e1(const int8_t *in, uint8_t *out, int len) -{ - return nrsc5_conv_decode(in, out, 9, len, 0561, 0657, 0711); -} - -int nrsc5_conv_decode_e2(const int8_t *in, uint8_t *out, int len) -{ - return nrsc5_conv_decode(in, out, 9, len, 0561, 0753, 0711); -} - -int nrsc5_conv_decode_e3(const int8_t *in, uint8_t *out, int len) -{ - return nrsc5_conv_decode(in, out, 9, len, 0561, 0753, 0711); -} +/* + * Viterbi decoder for convolutional codes + * + * Copyright (C) 2015 Ettus Research LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Author: Tom Tsou + */ + +#include "config.h" + +#include +#ifndef __APPLE__ +#include +#endif +#include +#include +#include + +#include "defines.h" +#include "conv.h" + +#include "conv_gen.h" +#if defined(HAVE_SSE3) +#include "conv_sse.h" +#elif defined(HAVE_NEON) +#include "conv_neon.h" +#endif + +#define TAIL_BITING_EXTRA 32 + +/* + * Trellis State + * + * state - Internal shift register value + * prev - Register values of previous 0 and 1 states + */ +struct vstate { + unsigned state; + unsigned prev[2]; +}; + +/* + * Trellis Object + * + * num_states - Number of states in the trellis + * sums - Accumulated path metrics + * outputs - Trellis ouput values + * vals - Input value that led to each state + */ +struct vtrellis { + int num_states; + int16_t *sums; + int16_t *outputs; + uint8_t *vals; +}; + +/* + * Viterbi Decoder + * + * n - Code order + * k - Constraint length + * len - Horizontal length of trellis + * recursive - Set to '1' if the code is recursive + * intrvl - Normalization interval + * trellis - Trellis object + * punc - Puncturing sequence + * paths - Trellis paths + */ +struct vdecoder { + int n; + int k; + int len; + int recursive; + int intrvl; + struct vtrellis *trellis; + int *punc; + int16_t **paths; + + void (*metric_func)(const int8_t *, const int16_t *, + int16_t *, int16_t *, int); +}; + +/* + * Aligned Memory Allocator + * + * SSE requires 16-byte memory alignment. We store relevant trellis values + * (accumulated sums, outputs, and path decisions) as 16 bit signed integers + * so the allocated memory is casted as such. + */ +#define SSE_ALIGN 16 + +static int16_t *vdec_malloc(size_t n) +{ +#if defined(HAVE_SSE3) && !defined(__APPLE__) + return (int16_t *) memalign(SSE_ALIGN, sizeof(int16_t) * n); +#else + return (int16_t *) malloc(sizeof(int16_t) * n); +#endif +} + +/* Left shift and mask for finding the previous state */ +static unsigned vstate_lshift(unsigned reg, int k, int val) +{ + unsigned mask; + + if (k == 5) + mask = 0x0e; + else if (k == 7) + mask = 0x3e; + else if (k == 9) + mask = 0xfe; + else + mask = 0; + + return ((reg << 1) & mask) | val; +} + +/* + * Populate non-recursive trellis state + * + * For a given state defined by the k-1 length shift register, find the + * value of the input bit that drove the trellis to that state. Then + * generate the N outputs of the generator polynomial at that state. + */ +static void gen_state_info(const struct lte_conv_code *code, + uint8_t *val, unsigned reg, int16_t *out) +{ + int i; + unsigned prev; + + /* Previous '0' state */ + prev = vstate_lshift(reg, code->k, 0); + + /* Compute output and unpack to NRZ */ + *val = (reg >> (code->k - 2)) & 0x01; + prev = prev | (unsigned) *val << (code->k - 1); + + for (i = 0; i < code->n; i++) + out[i] = PARITY(prev & code->gen[i]) * 2 - 1; +} + +/* + * Populate recursive trellis state + */ +static void gen_rec_state_info(const struct lte_conv_code *code, + uint8_t *val, unsigned reg, int16_t *out) +{ + int i; + unsigned prev, rec, mask; + + /* Previous '0' and '1' states */ + prev = vstate_lshift(reg, code->k, 0); + + /* Compute recursive input value (not the value shifted into register) */ + rec = (reg >> (code->k - 2)) & 0x01; + + if ((unsigned int) PARITY(prev & code->rgen) == rec) + *val = 0; + else + *val = 1; + + /* Compute outputs and unpack to NRZ */ + prev = prev | rec << (code->k - 1); + + if (code->k == 5) + mask = 0x0f; + else if (code->k == 7) + mask = 0x3f; + else + mask = 0xff; + + /* Check for recursive outputs */ + for (i = 0; i < code->n; i++) { + if (code->gen[i] & mask) + out[i] = PARITY(prev & code->gen[i]) * 2 - 1; + else + out[i] = *val * 2 - 1; + } +} + +/* Release the trellis */ +static void free_trellis(struct vtrellis *trellis) +{ + if (!trellis) + return; + + free(trellis->vals); + free(trellis->outputs); + free(trellis->sums); + free(trellis); +} + +#define NUM_STATES(K) (K == 9 ? 256 : (K == 7 ? 64 : 16)) + +/* + * Allocate and initialize the trellis object + * + * Initialization consists of generating the outputs and output value of a + * given state. Due to trellis symmetry, only one of the transition paths + * is used by the butterfly operation in the forward recursion, so only one + * set of N outputs is required per state variable. + */ +static struct vtrellis *generate_trellis(const struct lte_conv_code *code) +{ + int i; + struct vtrellis *trellis; + int16_t *out; + + int ns = NUM_STATES(code->k); + int olen = (code->n == 2) ? 2 : 4; + + trellis = (struct vtrellis *) calloc(1, sizeof(struct vtrellis)); + trellis->num_states = ns; + trellis->sums = vdec_malloc(ns); + trellis->outputs = vdec_malloc(ns * olen); + trellis->vals = (uint8_t *) malloc(ns * sizeof(uint8_t)); + + if (!trellis->sums || !trellis->outputs) + goto fail; + + /* Populate the trellis state objects */ + for (i = 0; i < ns; i++) { + out = &trellis->outputs[olen * i]; + + if (code->rgen) + gen_rec_state_info(code, &trellis->vals[i], i, out); + else + gen_state_info(code, &trellis->vals[i], i, out); + } + + return trellis; +fail: + free_trellis(trellis); + return NULL; +} + +/* + * Reset decoder + * + * Set accumulated path metrics to zero. For termination other than + * tail-biting, initialize the zero state as the encoder starting state. + * Intialize with the maximum accumulated sum at length equal to the + * constraint length. + */ +static void reset_decoder(struct vdecoder *dec, int term) +{ + int ns = dec->trellis->num_states; + + memset(dec->trellis->sums, 0, sizeof(int16_t) * ns); + + if (term != CONV_TERM_TAIL_BITING) + dec->trellis->sums[0] = INT8_MAX * dec->n * dec->k; +} + +static int _traceback(struct vdecoder *dec, + unsigned state, uint8_t *out, int len, int offset) +{ + int i; + unsigned path; + + for (i = len - 1; i >= 0; i--) { + path = dec->paths[i + offset][state] + 1; + out[i] = dec->trellis->vals[state]; + state = vstate_lshift(state, dec->k, path); + } + + return state; +} + +static void _traceback_rec(struct vdecoder *dec, + unsigned state, uint8_t *out, int len) +{ + int i; + unsigned path; + + for (i = len - 1; i >= 0; i--) { + path = dec->paths[i][state] + 1; + out[i] = path ^ dec->trellis->vals[state]; + state = vstate_lshift(state, dec->k, path); + } +} + +/* + * Traceback and generate decoded output + * + * For tail biting, find the largest accumulated path metric at the final state + * followed by two trace back passes. For zero flushing the final state is + * always zero with a single traceback path. + */ +static int traceback(struct vdecoder *dec, uint8_t *out, int term, int len) +{ + int i, sum, max_p = -1, max = -1; + unsigned path, state = 0; + + if (term == CONV_TERM_TAIL_BITING) { + for (i = 0; i < dec->trellis->num_states; i++) { + sum = dec->trellis->sums[i]; + if (sum > max) { + max_p = max; + max = sum; + state = i; + } + } + if (max < 0) + return -EPROTO; + for (i = dec->len - 1; i >= len + TAIL_BITING_EXTRA; i--) { + path = dec->paths[i][state] + 1; + state = vstate_lshift(state, dec->k, path); + } + } else { + for (i = dec->len - 1; i >= len; i--) { + path = dec->paths[i][state] + 1; + state = vstate_lshift(state, dec->k, path); + } + } + + if (dec->recursive) + _traceback_rec(dec, state, out, len); + else + state =_traceback(dec, state, out, len, term == CONV_TERM_TAIL_BITING ? TAIL_BITING_EXTRA : 0); + + /* Don't handle the odd case of recursize tail-biting codes */ + + return max - max_p; +} + +/* Release decoder object */ +static void free_vdec(struct vdecoder *dec) +{ + if (!dec) + return; + + free(dec->paths[0]); + free(dec->paths); + free_trellis(dec->trellis); + free(dec); +} + +/* + * Allocate decoder object + * + * Subtract the constraint length K on the normalization interval to + * accommodate the initialization path metric at state zero. + */ +static struct vdecoder *alloc_vdec(const struct lte_conv_code *code) +{ + int i, ns; + struct vdecoder *dec; + + ns = NUM_STATES(code->k); + + dec = (struct vdecoder *) calloc(1, sizeof(struct vdecoder)); + dec->n = code->n; + dec->k = code->k; + dec->recursive = code->rgen ? 1 : 0; + dec->intrvl = INT16_MAX / (dec->n * INT8_MAX) - dec->k; + + assert(dec->n == 3); + assert(dec->k == 7 || dec->k == 9); + + if (code->term == CONV_TERM_FLUSH) + dec->len = code->len + code->k - 1; + else + dec->len = code->len + TAIL_BITING_EXTRA * 2; + + dec->trellis = generate_trellis(code); + if (!dec->trellis) + goto fail; + + dec->paths = (int16_t **) malloc(sizeof(int16_t *) * dec->len); + dec->paths[0] = vdec_malloc(ns * dec->len); + for (i = 1; i < dec->len; i++) + dec->paths[i] = &dec->paths[0][i * ns]; + + return dec; +fail: + free_vdec(dec); + return NULL; +} + +/* + * Forward trellis recursion + * + * Generate branch metrics and path metrics with a combined function. Only + * accumulated path metric sums and path selections are stored. Normalize on + * the interval specified by the decoder. + */ +static void _conv_decode(struct vdecoder *dec, const int8_t *seq, int term, int len) +{ + int i, j = 0; + struct vtrellis *trellis = dec->trellis; + + if (term == CONV_TERM_TAIL_BITING) + j = len - TAIL_BITING_EXTRA; + + for (i = 0; i < dec->len; i++, j++) { + if (term == CONV_TERM_TAIL_BITING && j == len) + j = 0; + + if (dec->k == 7) + gen_metrics_k7_n3(&seq[dec->n * j], + trellis->outputs, + trellis->sums, + dec->paths[i], + !(i % dec->intrvl)); + else if (dec->k == 9) + gen_metrics_k9_n3(&seq[dec->n * j], + trellis->outputs, + trellis->sums, + dec->paths[i], + !(i % dec->intrvl)); + } +} + +static int nrsc5_conv_decode(const int8_t *in, uint8_t *out, int k, int len, unsigned int g1, unsigned int g2, unsigned int g3) +{ + const struct lte_conv_code code = { + .n = 3, + .k = k, + .len = len, + .gen = { g1, g2, g3 }, + .term = CONV_TERM_TAIL_BITING, + }; + int rc; + + struct vdecoder *vdec = alloc_vdec(&code); + if (!vdec) + return -EFAULT; + + reset_decoder(vdec, code.term); + + /* Propagate through the trellis with interval normalization */ + _conv_decode(vdec, in, code.term, code.len); + + rc = traceback(vdec, out, code.term, code.len); + + free_vdec(vdec); + return rc; +} + +int nrsc5_conv_decode_p1(const int8_t *in, uint8_t *out) +{ + return nrsc5_conv_decode(in, out, 7, P1_FRAME_LEN_FM, 0133, 0171, 0165); +} + +int nrsc5_conv_decode_pids(const int8_t *in, uint8_t *out) +{ + return nrsc5_conv_decode(in, out, 7, PIDS_FRAME_LEN, 0133, 0171, 0165); +} + +int nrsc5_conv_decode_p3(const int8_t *in, uint8_t *out) +{ + return nrsc5_conv_decode(in, out, 7, P3_FRAME_LEN_FM, 0133, 0171, 0165); +} + +int nrsc5_conv_decode_e1(const int8_t *in, uint8_t *out, int len) +{ + return nrsc5_conv_decode(in, out, 9, len, 0561, 0657, 0711); +} + +int nrsc5_conv_decode_e2(const int8_t *in, uint8_t *out, int len) +{ + return nrsc5_conv_decode(in, out, 9, len, 0561, 0753, 0711); +} + +int nrsc5_conv_decode_e3(const int8_t *in, uint8_t *out, int len) +{ + return nrsc5_conv_decode(in, out, 9, len, 0561, 0753, 0711); +} diff --git a/src/hddsp/conv_gen.h b/src/hddsp/conv_gen.h index ba916fe..61b2d8f 100644 --- a/src/hddsp/conv_gen.h +++ b/src/hddsp/conv_gen.h @@ -1,124 +1,124 @@ -/* - * Viterbi decoder for convolutional codes - * - * Copyright (C) 2015 Ettus Research LLC - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Author: Tom Tsou - */ - -#include -#include - -/* - * Add-Compare-Select (ACS-Butterfly) - * - * Compute 4 accumulated path metrics and 4 path selections. Note that path - * selections are store as -1 and 0 rather than 0 and 1. This is to match - * the output format of the sse packed compare instruction 'pmaxuw'. - */ -static void acs_butterfly(int state, int num_states, - int16_t metric, int16_t *sum, - int16_t *new_sum, int16_t *path) -{ - int state0, state1; - int sum0, sum1, sum2, sum3; - - state0 = *(sum + (2 * state + 0)); - state1 = *(sum + (2 * state + 1)); - - sum0 = state0 + metric; - sum1 = state1 - metric; - sum2 = state0 - metric; - sum3 = state1 + metric; - - if (sum0 > sum1) { - *new_sum = sum0; - *path = -1; - } else { - *new_sum = sum1; - *path = 0; - } - - if (sum2 > sum3) { - *(new_sum + num_states / 2) = sum2; - *(path + num_states / 2) = -1; - } else { - *(new_sum + num_states / 2) = sum3; - *(path + num_states / 2) = 0; - } -} - -/* Branch metrics unit N=3 */ -static void _gen_branch_metrics_n3(int num_states, const int8_t *seq, - const int16_t *out, int16_t *metrics) -{ - int i; - - for (i = 0; i < num_states / 2; i++) - metrics[i] = seq[0] * out[4 * i + 0] + - seq[1] * out[4 * i + 1] + - seq[2] * out[4 * i + 2]; -} - -/* Path metric unit */ -static void _gen_path_metrics(int num_states, int16_t *sums, - int16_t *metrics, int16_t *paths, int norm) -{ - int i; - int16_t min; - int16_t* new_sums = malloc(num_states * sizeof(int16_t)); - - for (i = 0; i < num_states / 2; i++) { - acs_butterfly(i, num_states, metrics[i], - sums, &new_sums[i], &paths[i]); - } - - if (norm) { - min = new_sums[0]; - for (i = 1; i < num_states; i++) { - if (new_sums[i] < min) - min = new_sums[i]; - } - - for (i = 0; i < num_states; i++) - new_sums[i] -= min; - } - - memcpy(sums, new_sums, num_states * sizeof(int16_t)); - free(new_sums); -} - -#if !defined(HAVE_SSE3) && !defined(HAVE_NEON) -static void gen_metrics_k7_n3(const int8_t *seq, const int16_t *out, - int16_t *sums, int16_t *paths, int norm) -{ - int16_t metrics[32]; - - _gen_branch_metrics_n3(64, seq, out, metrics); - _gen_path_metrics(64, sums, metrics, paths, norm); - -} -#endif - -static void gen_metrics_k9_n3(const int8_t *seq, const int16_t *out, - int16_t *sums, int16_t *paths, int norm) -{ - int16_t metrics[128]; - - _gen_branch_metrics_n3(256, seq, out, metrics); - _gen_path_metrics(256, sums, metrics, paths, norm); - -} +/* + * Viterbi decoder for convolutional codes + * + * Copyright (C) 2015 Ettus Research LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Author: Tom Tsou + */ + +#include +#include + +/* + * Add-Compare-Select (ACS-Butterfly) + * + * Compute 4 accumulated path metrics and 4 path selections. Note that path + * selections are store as -1 and 0 rather than 0 and 1. This is to match + * the output format of the sse packed compare instruction 'pmaxuw'. + */ +static void acs_butterfly(int state, int num_states, + int16_t metric, int16_t *sum, + int16_t *new_sum, int16_t *path) +{ + int state0, state1; + int sum0, sum1, sum2, sum3; + + state0 = *(sum + (2 * state + 0)); + state1 = *(sum + (2 * state + 1)); + + sum0 = state0 + metric; + sum1 = state1 - metric; + sum2 = state0 - metric; + sum3 = state1 + metric; + + if (sum0 > sum1) { + *new_sum = sum0; + *path = -1; + } else { + *new_sum = sum1; + *path = 0; + } + + if (sum2 > sum3) { + *(new_sum + num_states / 2) = sum2; + *(path + num_states / 2) = -1; + } else { + *(new_sum + num_states / 2) = sum3; + *(path + num_states / 2) = 0; + } +} + +/* Branch metrics unit N=3 */ +static void _gen_branch_metrics_n3(int num_states, const int8_t *seq, + const int16_t *out, int16_t *metrics) +{ + int i; + + for (i = 0; i < num_states / 2; i++) + metrics[i] = seq[0] * out[4 * i + 0] + + seq[1] * out[4 * i + 1] + + seq[2] * out[4 * i + 2]; +} + +/* Path metric unit */ +static void _gen_path_metrics(int num_states, int16_t *sums, + int16_t *metrics, int16_t *paths, int norm) +{ + int i; + int16_t min; + int16_t* new_sums = malloc(num_states * sizeof(int16_t)); + + for (i = 0; i < num_states / 2; i++) { + acs_butterfly(i, num_states, metrics[i], + sums, &new_sums[i], &paths[i]); + } + + if (norm) { + min = new_sums[0]; + for (i = 1; i < num_states; i++) { + if (new_sums[i] < min) + min = new_sums[i]; + } + + for (i = 0; i < num_states; i++) + new_sums[i] -= min; + } + + memcpy(sums, new_sums, num_states * sizeof(int16_t)); + free(new_sums); +} + +#if !defined(HAVE_SSE3) && !defined(HAVE_NEON) +static void gen_metrics_k7_n3(const int8_t *seq, const int16_t *out, + int16_t *sums, int16_t *paths, int norm) +{ + int16_t metrics[32]; + + _gen_branch_metrics_n3(64, seq, out, metrics); + _gen_path_metrics(64, sums, metrics, paths, norm); + +} +#endif + +static void gen_metrics_k9_n3(const int8_t *seq, const int16_t *out, + int16_t *sums, int16_t *paths, int norm) +{ + int16_t metrics[128]; + + _gen_branch_metrics_n3(256, seq, out, metrics); + _gen_path_metrics(256, sums, metrics, paths, norm); + +} diff --git a/src/hddsp/conv_neon.h b/src/hddsp/conv_neon.h index b3b682b..c7f1914 100644 --- a/src/hddsp/conv_neon.h +++ b/src/hddsp/conv_neon.h @@ -1,173 +1,173 @@ -/* - * Viterbi decoder for convolutional codes - ARM NEON - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Author: Andrew Wesie - * - * Based on conv_sse.h written by Tom Tsou . - */ - -#include -#include - -#define NEON_DEINTERLEAVE_K7(M0,M1,M2,M3,M4,M5,M6,M7, \ - M8,M9,M10,M11,M12,M13,M14,M15) \ -{ \ - int16x8x2_t tmp; \ - tmp = vuzpq_s16(M0, M1); \ - M8 = tmp.val[0]; M9 = tmp.val[1]; \ - tmp = vuzpq_s16(M2, M3); \ - M10 = tmp.val[0]; M11 = tmp.val[1]; \ - tmp = vuzpq_s16(M4, M5); \ - M12 = tmp.val[0]; M13 = tmp.val[1]; \ - tmp = vuzpq_s16(M6, M7); \ - M14 = tmp.val[0]; M15 = tmp.val[1]; \ -} -#define NEON_BRANCH_METRIC_N4(M0,M1,M2,M3,M4,M5) \ -{ \ - M0 = vmulq_s16(M4, M0); \ - M1 = vmulq_s16(M4, M1); \ - M2 = vmulq_s16(M4, M2); \ - M3 = vmulq_s16(M4, M3); \ - int16x4_t t1 = vpadd_s16(vpadd_s16(vget_low_s16(M0), vget_high_s16(M0)), vpadd_s16(vget_low_s16(M1), vget_high_s16(M1))); \ - int16x4_t t2 = vpadd_s16(vpadd_s16(vget_low_s16(M2), vget_high_s16(M2)), vpadd_s16(vget_low_s16(M3), vget_high_s16(M3))); \ - M5 = vcombine_s16(t1, t2); \ -} -#define NEON_BUTTERFLY(M0,M1,M2,M3,M4) \ -{ \ - M3 = vqaddq_s16(M0, M2); \ - M4 = vqsubq_s16(M1, M2); \ - M0 = vqsubq_s16(M0, M2); \ - M1 = vqaddq_s16(M1, M2); \ - M2 = vmaxq_s16(M3, M4); \ - M3 = vreinterpretq_s16_u16(vcgtq_s16(M3, M4)); \ - M4 = vmaxq_s16(M0, M1); \ - M1 = vreinterpretq_s16_u16(vcgtq_s16(M0, M1)); \ -} -#define NEON_NORMALIZE_K7(M0,M1,M2,M3,M4,M5,M6,M7,M8,M9,M10,M11) \ -{ \ - M8 = vminq_s16(M0, M1); \ - M9 = vminq_s16(M2, M3); \ - M10 = vminq_s16(M4, M5); \ - M11 = vminq_s16(M6, M7); \ - M8 = vminq_s16(M8, M9); \ - M10 = vminq_s16(M10, M11); \ - M8 = vminq_s16(M8, M10); \ - int16x4_t t = vpmin_s16(vget_low_s16(M8), vget_high_s16(M8)); \ - t = vpmin_s16(t, t); \ - t = vpmin_s16(t, t); \ - M8 = vdupq_lane_s16(t, 0); \ - M0 = vqsubq_s16(M0, M8); \ - M1 = vqsubq_s16(M1, M8); \ - M2 = vqsubq_s16(M2, M8); \ - M3 = vqsubq_s16(M3, M8); \ - M4 = vqsubq_s16(M4, M8); \ - M5 = vqsubq_s16(M5, M8); \ - M6 = vqsubq_s16(M6, M8); \ - M7 = vqsubq_s16(M7, M8); \ -} -static inline void _neon_metrics_k7_n4(const int16_t *val, const int16_t *out, - int16_t *sums, int16_t *paths, int norm) -{ - int16x8_t m0, m1, m2, m3, m4, m5, m6, m7; - int16x8_t m8, m9, m10, m11, m12, m13, m14, m15; - int16x4_t input; - - /* (PMU) Load accumulated path matrics */ - m0 = vld1q_s16(&sums[0]); - m1 = vld1q_s16(&sums[8]); - m2 = vld1q_s16(&sums[16]); - m3 = vld1q_s16(&sums[24]); - m4 = vld1q_s16(&sums[32]); - m5 = vld1q_s16(&sums[40]); - m6 = vld1q_s16(&sums[48]); - m7 = vld1q_s16(&sums[56]); - - /* (PMU) Deinterleave into even and odd packed registers */ - NEON_DEINTERLEAVE_K7(m0, m1, m2, m3 ,m4 ,m5, m6, m7, - m8, m9, m10, m11, m12, m13, m14, m15) - - /* (BMU) Load and expand 8-bit input out to 16-bits */ - input = vld1_s16(val); - m7 = vcombine_s16(input, input); - - /* (BMU) Load and compute branch metrics */ - m0 = vld1q_s16(&out[0]); - m1 = vld1q_s16(&out[8]); - m2 = vld1q_s16(&out[16]); - m3 = vld1q_s16(&out[24]); - - NEON_BRANCH_METRIC_N4(m0, m1, m2, m3, m7, m4) - - m0 = vld1q_s16(&out[32]); - m1 = vld1q_s16(&out[40]); - m2 = vld1q_s16(&out[48]); - m3 = vld1q_s16(&out[56]); - - NEON_BRANCH_METRIC_N4(m0, m1, m2, m3, m7, m5) - - m0 = vld1q_s16(&out[64]); - m1 = vld1q_s16(&out[72]); - m2 = vld1q_s16(&out[80]); - m3 = vld1q_s16(&out[88]); - - NEON_BRANCH_METRIC_N4(m0, m1, m2, m3, m7, m6) - - m0 = vld1q_s16(&out[96]); - m1 = vld1q_s16(&out[104]); - m2 = vld1q_s16(&out[112]); - m3 = vld1q_s16(&out[120]); - - NEON_BRANCH_METRIC_N4(m0, m1, m2, m3, m7, m7) - - /* (PMU) Butterflies: 0-15 */ - NEON_BUTTERFLY(m8, m9, m4, m0, m1) - NEON_BUTTERFLY(m10, m11, m5, m2, m3) - - vst1q_s16(&paths[0], m0); - vst1q_s16(&paths[8], m2); - vst1q_s16(&paths[32], m9); - vst1q_s16(&paths[40], m11); - - /* (PMU) Butterflies: 17-31 */ - NEON_BUTTERFLY(m12, m13, m6, m0, m2) - NEON_BUTTERFLY(m14, m15, m7, m9, m11) - - vst1q_s16(&paths[16], m0); - vst1q_s16(&paths[24], m9); - vst1q_s16(&paths[48], m13); - vst1q_s16(&paths[56], m15); - - if (norm) - NEON_NORMALIZE_K7(m4, m1, m5, m3, m6, m2, - m7, m11, m0, m8, m9, m10) - - vst1q_s16(&sums[0], m4); - vst1q_s16(&sums[8], m5); - vst1q_s16(&sums[16], m6); - vst1q_s16(&sums[24], m7); - vst1q_s16(&sums[32], m1); - vst1q_s16(&sums[40], m3); - vst1q_s16(&sums[48], m2); - vst1q_s16(&sums[56], m11); -} - -static inline void gen_metrics_k7_n3(const int8_t *val, const int16_t *out, - int16_t *sums, int16_t *paths, int norm) -{ - const int16_t _val[4] = { val[0], val[1], val[2], 0 }; - - _neon_metrics_k7_n4(_val, out, sums, paths, norm); -} +/* + * Viterbi decoder for convolutional codes - ARM NEON + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Author: Andrew Wesie + * + * Based on conv_sse.h written by Tom Tsou . + */ + +#include +#include + +#define NEON_DEINTERLEAVE_K7(M0,M1,M2,M3,M4,M5,M6,M7, \ + M8,M9,M10,M11,M12,M13,M14,M15) \ +{ \ + int16x8x2_t tmp; \ + tmp = vuzpq_s16(M0, M1); \ + M8 = tmp.val[0]; M9 = tmp.val[1]; \ + tmp = vuzpq_s16(M2, M3); \ + M10 = tmp.val[0]; M11 = tmp.val[1]; \ + tmp = vuzpq_s16(M4, M5); \ + M12 = tmp.val[0]; M13 = tmp.val[1]; \ + tmp = vuzpq_s16(M6, M7); \ + M14 = tmp.val[0]; M15 = tmp.val[1]; \ +} +#define NEON_BRANCH_METRIC_N4(M0,M1,M2,M3,M4,M5) \ +{ \ + M0 = vmulq_s16(M4, M0); \ + M1 = vmulq_s16(M4, M1); \ + M2 = vmulq_s16(M4, M2); \ + M3 = vmulq_s16(M4, M3); \ + int16x4_t t1 = vpadd_s16(vpadd_s16(vget_low_s16(M0), vget_high_s16(M0)), vpadd_s16(vget_low_s16(M1), vget_high_s16(M1))); \ + int16x4_t t2 = vpadd_s16(vpadd_s16(vget_low_s16(M2), vget_high_s16(M2)), vpadd_s16(vget_low_s16(M3), vget_high_s16(M3))); \ + M5 = vcombine_s16(t1, t2); \ +} +#define NEON_BUTTERFLY(M0,M1,M2,M3,M4) \ +{ \ + M3 = vqaddq_s16(M0, M2); \ + M4 = vqsubq_s16(M1, M2); \ + M0 = vqsubq_s16(M0, M2); \ + M1 = vqaddq_s16(M1, M2); \ + M2 = vmaxq_s16(M3, M4); \ + M3 = vreinterpretq_s16_u16(vcgtq_s16(M3, M4)); \ + M4 = vmaxq_s16(M0, M1); \ + M1 = vreinterpretq_s16_u16(vcgtq_s16(M0, M1)); \ +} +#define NEON_NORMALIZE_K7(M0,M1,M2,M3,M4,M5,M6,M7,M8,M9,M10,M11) \ +{ \ + M8 = vminq_s16(M0, M1); \ + M9 = vminq_s16(M2, M3); \ + M10 = vminq_s16(M4, M5); \ + M11 = vminq_s16(M6, M7); \ + M8 = vminq_s16(M8, M9); \ + M10 = vminq_s16(M10, M11); \ + M8 = vminq_s16(M8, M10); \ + int16x4_t t = vpmin_s16(vget_low_s16(M8), vget_high_s16(M8)); \ + t = vpmin_s16(t, t); \ + t = vpmin_s16(t, t); \ + M8 = vdupq_lane_s16(t, 0); \ + M0 = vqsubq_s16(M0, M8); \ + M1 = vqsubq_s16(M1, M8); \ + M2 = vqsubq_s16(M2, M8); \ + M3 = vqsubq_s16(M3, M8); \ + M4 = vqsubq_s16(M4, M8); \ + M5 = vqsubq_s16(M5, M8); \ + M6 = vqsubq_s16(M6, M8); \ + M7 = vqsubq_s16(M7, M8); \ +} +static inline void _neon_metrics_k7_n4(const int16_t *val, const int16_t *out, + int16_t *sums, int16_t *paths, int norm) +{ + int16x8_t m0, m1, m2, m3, m4, m5, m6, m7; + int16x8_t m8, m9, m10, m11, m12, m13, m14, m15; + int16x4_t input; + + /* (PMU) Load accumulated path matrics */ + m0 = vld1q_s16(&sums[0]); + m1 = vld1q_s16(&sums[8]); + m2 = vld1q_s16(&sums[16]); + m3 = vld1q_s16(&sums[24]); + m4 = vld1q_s16(&sums[32]); + m5 = vld1q_s16(&sums[40]); + m6 = vld1q_s16(&sums[48]); + m7 = vld1q_s16(&sums[56]); + + /* (PMU) Deinterleave into even and odd packed registers */ + NEON_DEINTERLEAVE_K7(m0, m1, m2, m3 ,m4 ,m5, m6, m7, + m8, m9, m10, m11, m12, m13, m14, m15) + + /* (BMU) Load and expand 8-bit input out to 16-bits */ + input = vld1_s16(val); + m7 = vcombine_s16(input, input); + + /* (BMU) Load and compute branch metrics */ + m0 = vld1q_s16(&out[0]); + m1 = vld1q_s16(&out[8]); + m2 = vld1q_s16(&out[16]); + m3 = vld1q_s16(&out[24]); + + NEON_BRANCH_METRIC_N4(m0, m1, m2, m3, m7, m4) + + m0 = vld1q_s16(&out[32]); + m1 = vld1q_s16(&out[40]); + m2 = vld1q_s16(&out[48]); + m3 = vld1q_s16(&out[56]); + + NEON_BRANCH_METRIC_N4(m0, m1, m2, m3, m7, m5) + + m0 = vld1q_s16(&out[64]); + m1 = vld1q_s16(&out[72]); + m2 = vld1q_s16(&out[80]); + m3 = vld1q_s16(&out[88]); + + NEON_BRANCH_METRIC_N4(m0, m1, m2, m3, m7, m6) + + m0 = vld1q_s16(&out[96]); + m1 = vld1q_s16(&out[104]); + m2 = vld1q_s16(&out[112]); + m3 = vld1q_s16(&out[120]); + + NEON_BRANCH_METRIC_N4(m0, m1, m2, m3, m7, m7) + + /* (PMU) Butterflies: 0-15 */ + NEON_BUTTERFLY(m8, m9, m4, m0, m1) + NEON_BUTTERFLY(m10, m11, m5, m2, m3) + + vst1q_s16(&paths[0], m0); + vst1q_s16(&paths[8], m2); + vst1q_s16(&paths[32], m9); + vst1q_s16(&paths[40], m11); + + /* (PMU) Butterflies: 17-31 */ + NEON_BUTTERFLY(m12, m13, m6, m0, m2) + NEON_BUTTERFLY(m14, m15, m7, m9, m11) + + vst1q_s16(&paths[16], m0); + vst1q_s16(&paths[24], m9); + vst1q_s16(&paths[48], m13); + vst1q_s16(&paths[56], m15); + + if (norm) + NEON_NORMALIZE_K7(m4, m1, m5, m3, m6, m2, + m7, m11, m0, m8, m9, m10) + + vst1q_s16(&sums[0], m4); + vst1q_s16(&sums[8], m5); + vst1q_s16(&sums[16], m6); + vst1q_s16(&sums[24], m7); + vst1q_s16(&sums[32], m1); + vst1q_s16(&sums[40], m3); + vst1q_s16(&sums[48], m2); + vst1q_s16(&sums[56], m11); +} + +static inline void gen_metrics_k7_n3(const int8_t *val, const int16_t *out, + int16_t *sums, int16_t *paths, int norm) +{ + const int16_t _val[4] = { val[0], val[1], val[2], 0 }; + + _neon_metrics_k7_n4(_val, out, sums, paths, norm); +} diff --git a/src/hddsp/conv_sse.h b/src/hddsp/conv_sse.h index 11cc459..8e48479 100644 --- a/src/hddsp/conv_sse.h +++ b/src/hddsp/conv_sse.h @@ -1,323 +1,323 @@ -/* - * Viterbi decoder for convolutional codes - Intel SSE/AVX - * - * Copyright (C) 2015 Ettus Research LLC - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Author: Tom Tsou - */ - -#include "config.h" - -#include -#include -#include - -#if defined(HAVE_SSE4_1) || defined(HAVE_SSE41) -#include -#endif - -#ifdef HAVE_AVX2 -#include -#endif - -/* - * Octo-Viterbi butterfly - * - * Compute 8-wide butterfly generating 16 path decisions and 16 accumulated - * sums. Inputs all packed 16-bit integers in three 128-bit XMM registers. - * Two intermediate registers are used and results are set in the upper 4 - * registers. - * - * Input: - * M0 - Path metrics 0 (packed 16-bit integers) - * M1 - Path metrics 1 (packed 16-bit integers) - * M2 - Branch metrics (packed 16-bit integers) - * - * Output: - * M2 - Selected and accumulated path metrics 0 - * M4 - Selected and accumulated path metrics 1 - * M3 - Path selections 0 - * M1 - Path selections 1 - */ -#define SSE_BUTTERFLY(M0,M1,M2,M3,M4) \ -{ \ - M3 = _mm_adds_epi16(M0, M2); \ - M4 = _mm_subs_epi16(M1, M2); \ - M0 = _mm_subs_epi16(M0, M2); \ - M1 = _mm_adds_epi16(M1, M2); \ - M2 = _mm_max_epi16(M3, M4); \ - M3 = _mm_cmpgt_epi16(M3, M4); \ - M4 = _mm_max_epi16(M0, M1); \ - M1 = _mm_cmpgt_epi16(M0, M1); \ -} - -#define _I8_SHUFFLE_MASK 15, 14, 11, 10, 7, 6, 3, 2, 13, 12, 9, 8, 5, 4, 1, 0 - -/* - * Two lane deinterleaving K = 7 - * - * Take 64 interleaved 16-bit integers and deinterleave to 8 packed 128-bit - * registers. The operation summarized below. 16 registers are used with the - * lower 8 as input and upper 8 as output. - * - * In - 10101010 10101010 10101010 10101010 ... - * Out - 00000000 11111111 00000000 11111111 ... - * - * Input: - * M0:7 - Packed 16-bit integers - * - * Output: - * M8:15 - Deinterleaved packed 16-bit integers - */ -#define SSE_DEINTERLEAVE_K7(M0,M1,M2,M3,M4,M5,M6,M7, \ - M8,M9,M10,M11,M12,M13,M14,M15) \ -{ \ - M8 = _mm_set_epi8(_I8_SHUFFLE_MASK); \ - M0 = _mm_shuffle_epi8(M0, M8); \ - M1 = _mm_shuffle_epi8(M1, M8); \ - M2 = _mm_shuffle_epi8(M2, M8); \ - M3 = _mm_shuffle_epi8(M3, M8); \ - M4 = _mm_shuffle_epi8(M4, M8); \ - M5 = _mm_shuffle_epi8(M5, M8); \ - M6 = _mm_shuffle_epi8(M6, M8); \ - M7 = _mm_shuffle_epi8(M7, M8); \ - M8 = _mm_unpacklo_epi64(M0, M1); \ - M9 = _mm_unpackhi_epi64(M0, M1); \ - M10 = _mm_unpacklo_epi64(M2, M3); \ - M11 = _mm_unpackhi_epi64(M2, M3); \ - M12 = _mm_unpacklo_epi64(M4, M5); \ - M13 = _mm_unpackhi_epi64(M4, M5); \ - M14 = _mm_unpacklo_epi64(M6, M7); \ - M15 = _mm_unpackhi_epi64(M6, M7); \ -} - -/* - * Generate branch metrics N = 4: - * - * Compute 8 branch metrics from trellis outputs and input values. This - * macro is reused for N less than 4 where the extra soft input bits are - * padded. - * - * Input: - * M0:3 - 8 x 4 packed 16-bit trellis outputs - * M4 - Expanded and packed 16-bit input value - * - * Output: - * M5 - 8 computed 16-bit branch metrics - */ -#define SSE_BRANCH_METRIC_N4(M0,M1,M2,M3,M4,M5) \ -{ \ - M0 = _mm_sign_epi16(M4, M0); \ - M1 = _mm_sign_epi16(M4, M1); \ - M2 = _mm_sign_epi16(M4, M2); \ - M3 = _mm_sign_epi16(M4, M3); \ - M0 = _mm_hadds_epi16(M0, M1); \ - M1 = _mm_hadds_epi16(M2, M3); \ - M5 = _mm_hadds_epi16(M0, M1); \ -} - -/* - * Broadcast 16-bit integer - * - * Repeat the low 16-bit integer to all elements of the 128-bit SSE - * register. Only AVX2 has a dedicated broadcast instruction; use repeat - * unpacks for SSE only architectures. This is a destructive operation and - * the source register is overwritten. - * - * Input: - * M0 - Low 16-bit element is read - * - * Output: - * M0 - Contains broadcasted values - */ -#ifdef HAVE_AVX2 -#define SSE_BROADCAST(M0) \ -{ \ - M0 = _mm_broadcastw_epi16(M0); \ -} -#else -#define SSE_BROADCAST(M0) \ -{ \ - M0 = _mm_unpacklo_epi16(M0, M0); \ - M0 = _mm_unpacklo_epi32(M0, M0); \ - M0 = _mm_unpacklo_epi64(M0, M0); \ -} -#endif - -/* - * Horizontal minimum - * - * Compute horizontal minimum of packed unsigned 16-bit integers and place - * result in the low 16-bit element of the source register. Only SSE 4.1 - * has a dedicated minpos instruction. One internmediate register is used - * if SSE 4.1 is not available. This is a destructive operation and the - * source register is overwritten. - * - * Input: - * M0 - Packed unsigned 16-bit integers - * - * Output: - * M0 - Minimum value placed in low 16-bit element - */ -#if defined(HAVE_SSE4_1) || defined(HAVE_SSE41) -#define SSE_MINPOS(M0,M1) \ -{ \ - M0 = _mm_minpos_epu16(M0); \ -} -#else -#define SSE_MINPOS(M0,M1) \ -{ \ - M1 = _mm_shuffle_epi32(M0, _MM_SHUFFLE(0, 0, 3, 2)); \ - M0 = _mm_min_epi16(M0, M1); \ - M1 = _mm_shufflelo_epi16(M0, _MM_SHUFFLE(0, 0, 3, 2)); \ - M0 = _mm_min_epi16(M0, M1); \ - M1 = _mm_shufflelo_epi16(M0, _MM_SHUFFLE(0, 0, 0, 1)); \ - M0 = _mm_min_epi16(M0, M1); \ -} -#endif - -/* - * Normalize state metrics K = 7: - * - * Compute 64-wide normalization by subtracting the smallest value from - * all values. Inputs are 8 registers of accumulated sums and 4 temporary - * registers. Normalized results are returned in the originating locations. - * - * Input: - * M0:7 - Path metricss 0:7 (packed 16-bit integers) - * - * Output: - * M0:7 - Normalized path metrics 0:7 - */ -#define SSE_NORMALIZE_K7(M0,M1,M2,M3,M4,M5,M6,M7,M8,M9,M10,M11) \ -{ \ - M8 = _mm_min_epi16(M0, M1); \ - M9 = _mm_min_epi16(M2, M3); \ - M10 = _mm_min_epi16(M4, M5); \ - M11 = _mm_min_epi16(M6, M7); \ - M8 = _mm_min_epi16(M8, M9); \ - M10 = _mm_min_epi16(M10, M11); \ - M8 = _mm_min_epi16(M8, M10); \ - SSE_MINPOS(M8, M9) \ - SSE_BROADCAST(M8) \ - M0 = _mm_subs_epi16(M0, M8); \ - M1 = _mm_subs_epi16(M1, M8); \ - M2 = _mm_subs_epi16(M2, M8); \ - M3 = _mm_subs_epi16(M3, M8); \ - M4 = _mm_subs_epi16(M4, M8); \ - M5 = _mm_subs_epi16(M5, M8); \ - M6 = _mm_subs_epi16(M6, M8); \ - M7 = _mm_subs_epi16(M7, M8); \ -} - -/* - * Combined BMU/PMU (K=7, N=3 and N=4) - * - * Compute branch metrics followed by path metrics for half rate 64-state - * trellis. 32 butterfly operations are computed. Deinterleave path - * metrics before computing branch metrics as in the half rate case. - */ -static inline void _sse_metrics_k7_n4(const int16_t *val, const int16_t *out, - int16_t *sums, int16_t *paths, int norm) -{ - __m128i m0, m1, m2, m3, m4, m5, m6, m7; - __m128i m8, m9, m10, m11, m12, m13, m14, m15; - - /* (PMU) Load accumulated path matrics */ - m0 = _mm_load_si128((__m128i *) &sums[0]); - m1 = _mm_load_si128((__m128i *) &sums[8]); - m2 = _mm_load_si128((__m128i *) &sums[16]); - m3 = _mm_load_si128((__m128i *) &sums[24]); - m4 = _mm_load_si128((__m128i *) &sums[32]); - m5 = _mm_load_si128((__m128i *) &sums[40]); - m6 = _mm_load_si128((__m128i *) &sums[48]); - m7 = _mm_load_si128((__m128i *) &sums[56]); - - /* (PMU) Deinterleave into even and odd packed registers */ - SSE_DEINTERLEAVE_K7(m0, m1, m2, m3 ,m4 ,m5, m6, m7, - m8, m9, m10, m11, m12, m13, m14, m15) - - /* (BMU) Load and expand 8-bit input out to 16-bits */ - m7 = _mm_loadu_si128((__m128i*) val); - - /* (BMU) Load and compute branch metrics */ - m0 = _mm_load_si128((__m128i *) &out[0]); - m1 = _mm_load_si128((__m128i *) &out[8]); - m2 = _mm_load_si128((__m128i *) &out[16]); - m3 = _mm_load_si128((__m128i *) &out[24]); - - SSE_BRANCH_METRIC_N4(m0, m1, m2, m3, m7, m4) - - m0 = _mm_load_si128((__m128i *) &out[32]); - m1 = _mm_load_si128((__m128i *) &out[40]); - m2 = _mm_load_si128((__m128i *) &out[48]); - m3 = _mm_load_si128((__m128i *) &out[56]); - - SSE_BRANCH_METRIC_N4(m0, m1, m2, m3, m7, m5) - - m0 = _mm_load_si128((__m128i *) &out[64]); - m1 = _mm_load_si128((__m128i *) &out[72]); - m2 = _mm_load_si128((__m128i *) &out[80]); - m3 = _mm_load_si128((__m128i *) &out[88]); - - SSE_BRANCH_METRIC_N4(m0, m1, m2, m3, m7, m6) - - m0 = _mm_load_si128((__m128i *) &out[96]); - m1 = _mm_load_si128((__m128i *) &out[104]); - m2 = _mm_load_si128((__m128i *) &out[112]); - m3 = _mm_load_si128((__m128i *) &out[120]); - - SSE_BRANCH_METRIC_N4(m0, m1, m2, m3, m7, m7) - - /* (PMU) Butterflies: 0-15 */ - SSE_BUTTERFLY(m8, m9, m4, m0, m1) - SSE_BUTTERFLY(m10, m11, m5, m2, m3) - - _mm_store_si128((__m128i *) &paths[0], m0); - _mm_store_si128((__m128i *) &paths[8], m2); - _mm_store_si128((__m128i *) &paths[32], m9); - _mm_store_si128((__m128i *) &paths[40], m11); - - /* (PMU) Butterflies: 17-31 */ - SSE_BUTTERFLY(m12, m13, m6, m0, m2) - SSE_BUTTERFLY(m14, m15, m7, m9, m11) - - _mm_store_si128((__m128i *) &paths[16], m0); - _mm_store_si128((__m128i *) &paths[24], m9); - _mm_store_si128((__m128i *) &paths[48], m13); - _mm_store_si128((__m128i *) &paths[56], m15); - - if (norm) - SSE_NORMALIZE_K7(m4, m1, m5, m3, m6, m2, - m7, m11, m0, m8, m9, m10) - - _mm_store_si128((__m128i *) &sums[0], m4); - _mm_store_si128((__m128i *) &sums[8], m5); - _mm_store_si128((__m128i *) &sums[16], m6); - _mm_store_si128((__m128i *) &sums[24], m7); - _mm_store_si128((__m128i *) &sums[32], m1); - _mm_store_si128((__m128i *) &sums[40], m3); - _mm_store_si128((__m128i *) &sums[48], m2); - _mm_store_si128((__m128i *) &sums[56], m11); -} - -static void gen_metrics_k7_n3(const int8_t *val, const int16_t *out, - int16_t *sums, int16_t *paths, int norm) -{ - const int16_t _val[8] = { val[0], val[1], val[2], 0, val[0], val[1], val[2], 0 }; - - _sse_metrics_k7_n4(_val, out, sums, paths, norm); -} +/* + * Viterbi decoder for convolutional codes - Intel SSE/AVX + * + * Copyright (C) 2015 Ettus Research LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Author: Tom Tsou + */ + +#include "config.h" + +#include +#include +#include + +#if defined(HAVE_SSE4_1) || defined(HAVE_SSE41) +#include +#endif + +#ifdef HAVE_AVX2 +#include +#endif + +/* + * Octo-Viterbi butterfly + * + * Compute 8-wide butterfly generating 16 path decisions and 16 accumulated + * sums. Inputs all packed 16-bit integers in three 128-bit XMM registers. + * Two intermediate registers are used and results are set in the upper 4 + * registers. + * + * Input: + * M0 - Path metrics 0 (packed 16-bit integers) + * M1 - Path metrics 1 (packed 16-bit integers) + * M2 - Branch metrics (packed 16-bit integers) + * + * Output: + * M2 - Selected and accumulated path metrics 0 + * M4 - Selected and accumulated path metrics 1 + * M3 - Path selections 0 + * M1 - Path selections 1 + */ +#define SSE_BUTTERFLY(M0,M1,M2,M3,M4) \ +{ \ + M3 = _mm_adds_epi16(M0, M2); \ + M4 = _mm_subs_epi16(M1, M2); \ + M0 = _mm_subs_epi16(M0, M2); \ + M1 = _mm_adds_epi16(M1, M2); \ + M2 = _mm_max_epi16(M3, M4); \ + M3 = _mm_cmpgt_epi16(M3, M4); \ + M4 = _mm_max_epi16(M0, M1); \ + M1 = _mm_cmpgt_epi16(M0, M1); \ +} + +#define _I8_SHUFFLE_MASK 15, 14, 11, 10, 7, 6, 3, 2, 13, 12, 9, 8, 5, 4, 1, 0 + +/* + * Two lane deinterleaving K = 7 + * + * Take 64 interleaved 16-bit integers and deinterleave to 8 packed 128-bit + * registers. The operation summarized below. 16 registers are used with the + * lower 8 as input and upper 8 as output. + * + * In - 10101010 10101010 10101010 10101010 ... + * Out - 00000000 11111111 00000000 11111111 ... + * + * Input: + * M0:7 - Packed 16-bit integers + * + * Output: + * M8:15 - Deinterleaved packed 16-bit integers + */ +#define SSE_DEINTERLEAVE_K7(M0,M1,M2,M3,M4,M5,M6,M7, \ + M8,M9,M10,M11,M12,M13,M14,M15) \ +{ \ + M8 = _mm_set_epi8(_I8_SHUFFLE_MASK); \ + M0 = _mm_shuffle_epi8(M0, M8); \ + M1 = _mm_shuffle_epi8(M1, M8); \ + M2 = _mm_shuffle_epi8(M2, M8); \ + M3 = _mm_shuffle_epi8(M3, M8); \ + M4 = _mm_shuffle_epi8(M4, M8); \ + M5 = _mm_shuffle_epi8(M5, M8); \ + M6 = _mm_shuffle_epi8(M6, M8); \ + M7 = _mm_shuffle_epi8(M7, M8); \ + M8 = _mm_unpacklo_epi64(M0, M1); \ + M9 = _mm_unpackhi_epi64(M0, M1); \ + M10 = _mm_unpacklo_epi64(M2, M3); \ + M11 = _mm_unpackhi_epi64(M2, M3); \ + M12 = _mm_unpacklo_epi64(M4, M5); \ + M13 = _mm_unpackhi_epi64(M4, M5); \ + M14 = _mm_unpacklo_epi64(M6, M7); \ + M15 = _mm_unpackhi_epi64(M6, M7); \ +} + +/* + * Generate branch metrics N = 4: + * + * Compute 8 branch metrics from trellis outputs and input values. This + * macro is reused for N less than 4 where the extra soft input bits are + * padded. + * + * Input: + * M0:3 - 8 x 4 packed 16-bit trellis outputs + * M4 - Expanded and packed 16-bit input value + * + * Output: + * M5 - 8 computed 16-bit branch metrics + */ +#define SSE_BRANCH_METRIC_N4(M0,M1,M2,M3,M4,M5) \ +{ \ + M0 = _mm_sign_epi16(M4, M0); \ + M1 = _mm_sign_epi16(M4, M1); \ + M2 = _mm_sign_epi16(M4, M2); \ + M3 = _mm_sign_epi16(M4, M3); \ + M0 = _mm_hadds_epi16(M0, M1); \ + M1 = _mm_hadds_epi16(M2, M3); \ + M5 = _mm_hadds_epi16(M0, M1); \ +} + +/* + * Broadcast 16-bit integer + * + * Repeat the low 16-bit integer to all elements of the 128-bit SSE + * register. Only AVX2 has a dedicated broadcast instruction; use repeat + * unpacks for SSE only architectures. This is a destructive operation and + * the source register is overwritten. + * + * Input: + * M0 - Low 16-bit element is read + * + * Output: + * M0 - Contains broadcasted values + */ +#ifdef HAVE_AVX2 +#define SSE_BROADCAST(M0) \ +{ \ + M0 = _mm_broadcastw_epi16(M0); \ +} +#else +#define SSE_BROADCAST(M0) \ +{ \ + M0 = _mm_unpacklo_epi16(M0, M0); \ + M0 = _mm_unpacklo_epi32(M0, M0); \ + M0 = _mm_unpacklo_epi64(M0, M0); \ +} +#endif + +/* + * Horizontal minimum + * + * Compute horizontal minimum of packed unsigned 16-bit integers and place + * result in the low 16-bit element of the source register. Only SSE 4.1 + * has a dedicated minpos instruction. One internmediate register is used + * if SSE 4.1 is not available. This is a destructive operation and the + * source register is overwritten. + * + * Input: + * M0 - Packed unsigned 16-bit integers + * + * Output: + * M0 - Minimum value placed in low 16-bit element + */ +#if defined(HAVE_SSE4_1) || defined(HAVE_SSE41) +#define SSE_MINPOS(M0,M1) \ +{ \ + M0 = _mm_minpos_epu16(M0); \ +} +#else +#define SSE_MINPOS(M0,M1) \ +{ \ + M1 = _mm_shuffle_epi32(M0, _MM_SHUFFLE(0, 0, 3, 2)); \ + M0 = _mm_min_epi16(M0, M1); \ + M1 = _mm_shufflelo_epi16(M0, _MM_SHUFFLE(0, 0, 3, 2)); \ + M0 = _mm_min_epi16(M0, M1); \ + M1 = _mm_shufflelo_epi16(M0, _MM_SHUFFLE(0, 0, 0, 1)); \ + M0 = _mm_min_epi16(M0, M1); \ +} +#endif + +/* + * Normalize state metrics K = 7: + * + * Compute 64-wide normalization by subtracting the smallest value from + * all values. Inputs are 8 registers of accumulated sums and 4 temporary + * registers. Normalized results are returned in the originating locations. + * + * Input: + * M0:7 - Path metricss 0:7 (packed 16-bit integers) + * + * Output: + * M0:7 - Normalized path metrics 0:7 + */ +#define SSE_NORMALIZE_K7(M0,M1,M2,M3,M4,M5,M6,M7,M8,M9,M10,M11) \ +{ \ + M8 = _mm_min_epi16(M0, M1); \ + M9 = _mm_min_epi16(M2, M3); \ + M10 = _mm_min_epi16(M4, M5); \ + M11 = _mm_min_epi16(M6, M7); \ + M8 = _mm_min_epi16(M8, M9); \ + M10 = _mm_min_epi16(M10, M11); \ + M8 = _mm_min_epi16(M8, M10); \ + SSE_MINPOS(M8, M9) \ + SSE_BROADCAST(M8) \ + M0 = _mm_subs_epi16(M0, M8); \ + M1 = _mm_subs_epi16(M1, M8); \ + M2 = _mm_subs_epi16(M2, M8); \ + M3 = _mm_subs_epi16(M3, M8); \ + M4 = _mm_subs_epi16(M4, M8); \ + M5 = _mm_subs_epi16(M5, M8); \ + M6 = _mm_subs_epi16(M6, M8); \ + M7 = _mm_subs_epi16(M7, M8); \ +} + +/* + * Combined BMU/PMU (K=7, N=3 and N=4) + * + * Compute branch metrics followed by path metrics for half rate 64-state + * trellis. 32 butterfly operations are computed. Deinterleave path + * metrics before computing branch metrics as in the half rate case. + */ +static inline void _sse_metrics_k7_n4(const int16_t *val, const int16_t *out, + int16_t *sums, int16_t *paths, int norm) +{ + __m128i m0, m1, m2, m3, m4, m5, m6, m7; + __m128i m8, m9, m10, m11, m12, m13, m14, m15; + + /* (PMU) Load accumulated path matrics */ + m0 = _mm_load_si128((__m128i *) &sums[0]); + m1 = _mm_load_si128((__m128i *) &sums[8]); + m2 = _mm_load_si128((__m128i *) &sums[16]); + m3 = _mm_load_si128((__m128i *) &sums[24]); + m4 = _mm_load_si128((__m128i *) &sums[32]); + m5 = _mm_load_si128((__m128i *) &sums[40]); + m6 = _mm_load_si128((__m128i *) &sums[48]); + m7 = _mm_load_si128((__m128i *) &sums[56]); + + /* (PMU) Deinterleave into even and odd packed registers */ + SSE_DEINTERLEAVE_K7(m0, m1, m2, m3 ,m4 ,m5, m6, m7, + m8, m9, m10, m11, m12, m13, m14, m15) + + /* (BMU) Load and expand 8-bit input out to 16-bits */ + m7 = _mm_loadu_si128((__m128i*) val); + + /* (BMU) Load and compute branch metrics */ + m0 = _mm_load_si128((__m128i *) &out[0]); + m1 = _mm_load_si128((__m128i *) &out[8]); + m2 = _mm_load_si128((__m128i *) &out[16]); + m3 = _mm_load_si128((__m128i *) &out[24]); + + SSE_BRANCH_METRIC_N4(m0, m1, m2, m3, m7, m4) + + m0 = _mm_load_si128((__m128i *) &out[32]); + m1 = _mm_load_si128((__m128i *) &out[40]); + m2 = _mm_load_si128((__m128i *) &out[48]); + m3 = _mm_load_si128((__m128i *) &out[56]); + + SSE_BRANCH_METRIC_N4(m0, m1, m2, m3, m7, m5) + + m0 = _mm_load_si128((__m128i *) &out[64]); + m1 = _mm_load_si128((__m128i *) &out[72]); + m2 = _mm_load_si128((__m128i *) &out[80]); + m3 = _mm_load_si128((__m128i *) &out[88]); + + SSE_BRANCH_METRIC_N4(m0, m1, m2, m3, m7, m6) + + m0 = _mm_load_si128((__m128i *) &out[96]); + m1 = _mm_load_si128((__m128i *) &out[104]); + m2 = _mm_load_si128((__m128i *) &out[112]); + m3 = _mm_load_si128((__m128i *) &out[120]); + + SSE_BRANCH_METRIC_N4(m0, m1, m2, m3, m7, m7) + + /* (PMU) Butterflies: 0-15 */ + SSE_BUTTERFLY(m8, m9, m4, m0, m1) + SSE_BUTTERFLY(m10, m11, m5, m2, m3) + + _mm_store_si128((__m128i *) &paths[0], m0); + _mm_store_si128((__m128i *) &paths[8], m2); + _mm_store_si128((__m128i *) &paths[32], m9); + _mm_store_si128((__m128i *) &paths[40], m11); + + /* (PMU) Butterflies: 17-31 */ + SSE_BUTTERFLY(m12, m13, m6, m0, m2) + SSE_BUTTERFLY(m14, m15, m7, m9, m11) + + _mm_store_si128((__m128i *) &paths[16], m0); + _mm_store_si128((__m128i *) &paths[24], m9); + _mm_store_si128((__m128i *) &paths[48], m13); + _mm_store_si128((__m128i *) &paths[56], m15); + + if (norm) + SSE_NORMALIZE_K7(m4, m1, m5, m3, m6, m2, + m7, m11, m0, m8, m9, m10) + + _mm_store_si128((__m128i *) &sums[0], m4); + _mm_store_si128((__m128i *) &sums[8], m5); + _mm_store_si128((__m128i *) &sums[16], m6); + _mm_store_si128((__m128i *) &sums[24], m7); + _mm_store_si128((__m128i *) &sums[32], m1); + _mm_store_si128((__m128i *) &sums[40], m3); + _mm_store_si128((__m128i *) &sums[48], m2); + _mm_store_si128((__m128i *) &sums[56], m11); +} + +static void gen_metrics_k7_n3(const int8_t *val, const int16_t *out, + int16_t *sums, int16_t *paths, int norm) +{ + const int16_t _val[8] = { val[0], val[1], val[2], 0, val[0], val[1], val[2], 0 }; + + _sse_metrics_k7_n4(_val, out, sums, paths, norm); +} diff --git a/src/hddsp/decode.c b/src/hddsp/decode.c index 2e4212a..2f130bc 100644 --- a/src/hddsp/decode.c +++ b/src/hddsp/decode.c @@ -1,358 +1,358 @@ -/* - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include - -#include "conv.h" -#include "decode.h" -#include "input.h" -#include "pids.h" -#include "private.h" - -/* 1012s.pdf figure 10-4 */ -static int bl_delay[] = { 2, 1, 5 }; -static int ml_delay[] = { 11, 6, 7 }; -static int bu_delay[] = { 10, 8, 9 }; -static int mu_delay[] = { 4, 3, 0 }; -static int el_delay[] = { 0, 1 }; -static int eu_delay[] = { 2, 3, 5, 4 }; - -/* 1012s.pdf figure 10-5 */ -static int pids_il_delay[] = { 0, 1, 12, 13, 6, 5, 18, 17, 11, 7, 23, 19 }; -static int pids_iu_delay[] = { 2, 4, 14, 16, 3, 8, 15, 20, 9, 10, 21, 22 }; - -static int bit_map(unsigned char matrix[PARTITION_WIDTH_AM * BLKSZ * 8], int b, int k, int p) -{ - int col = (9*k) % 25; - int row = (11*col + 16*(k/25) + 11*(k/50)) % 32; - return (matrix[PARTITION_WIDTH_AM * (b*BLKSZ + row) + col] >> p) & 1; -} - -static void interleaver_ma1(decode_t *st) -{ - int b, k, p; - for (int n = 0; n < 18000; n++) - { - b = n/2250; - k = (n + n/750 + 1) % 750; - p = n % 3; - st->bl[n] = bit_map(st->buffer_pl, b, k, p); - - b = (3*n + 3) % 8; - k = (n + n/3000 + 3) % 750; - p = 3 + (n % 3); - st->ml[DIVERSITY_DELAY_AM + n] = bit_map(st->buffer_pl, b, k, p); - - b = n/2250; - k = (n + n/750) % 750; - p = n % 3; - st->bu[n] = bit_map(st->buffer_pu, b, k, p); - - b = (3*n) % 8; - k = (n + n/3000 + 2) % 750; - p = 3 + (n % 3); - st->mu[DIVERSITY_DELAY_AM + n] = bit_map(st->buffer_pu, b, k, p); - } - for (int n = 0; n < 12000; n++) - { - b = (3*n + n/3000) % 8; - k = (n + (n/6000)) % 750; - p = n % 2; - st->el[n] = bit_map(st->buffer_t, b, k, p); - } - for (int n = 0; n < 24000; n++) - { - b = (3*n + n/3000 + 2*(n/12000)) % 8; - k = (n + (n/6000)) % 750; - p = n % 4; - st->eu[n] = bit_map(st->buffer_s, b, k, p); - } - - for (int i = 0; i < 6000; i++) - { - for (int j = 0; j < 3; j++) - { - st->p1_am[i*12 + bl_delay[j]] = st->bl[i*3 + j]; - st->p1_am[i*12 + ml_delay[j]] = st->ml[i*3 + j]; - st->p1_am[i*12 + bu_delay[j]] = st->bu[i*3 + j]; - st->p1_am[i*12 + mu_delay[j]] = st->mu[i*3 + j]; - } - for (int j = 0; j < 2; j++) - { - st->p3_am[i*6 + el_delay[j]] = st->el[i*2 + j]; - } - for (int j = 0; j < 4; j++) - { - st->p3_am[i*6 + eu_delay[j]] = st->eu[i*4 + j]; - } - } - - memmove(st->ml, st->ml + 18000, DIVERSITY_DELAY_AM); - memmove(st->mu, st->mu + 18000, DIVERSITY_DELAY_AM); - - int offset = 0; - for (int i = 0; i < 8 * P1_FRAME_LEN_AM * 3; i++) - { - switch (i % 15) - { - case 1: - case 4: - case 7: - st->viterbi_p1_am[i] = 0; - break; - default: - st->viterbi_p1_am[i] = st->p1_am[offset++] ? 1 : -1; - } - } - - offset = 0; - for (int i = 0; i < P3_FRAME_LEN_AM * 3; i++) - { - switch (i % 6) - { - case 1: - case 4: - case 5: - st->viterbi_p3_am[i] = 0; - break; - default: - st->viterbi_p3_am[i] = st->p3_am[offset++] ? 1 : -1; - } - } -} - -// calculate number of bit errors by re-encoding and comparing to the input -static int bit_errors(int8_t *coded, uint8_t *decoded, int k, int frame_len, - unsigned int g1, unsigned int g2, unsigned int g3, - uint8_t *puncture, int puncture_len) -{ - uint16_t r = 0; - unsigned int i, j, errors = 0; - - // tail biting - for (i = 0; i < (k-1); i++) - r = (r >> 1) | (decoded[frame_len - (k-1) + i] << (k-1)); - - for (i = 0, j = 0; i < frame_len; i++, j += 3) - { - // shift in new bit - r = (r >> 1) | (decoded[i] << (k-1)); - - if (puncture[j % puncture_len] && ((coded[j] > 0) != PARITY(r & g1))) - errors++; - if (puncture[(j+1) % puncture_len] && ((coded[j+1] > 0) != PARITY(r & g2))) - errors++; - if (puncture[(j+2) % puncture_len] && ((coded[j+2] > 0) != PARITY(r & g3))) - errors++; - } - - return errors; -} - -static int bit_errors_p1_fm(int8_t *coded, uint8_t *decoded) -{ - uint8_t puncture[] = {1, 1, 1, 1, 1, 0}; - return bit_errors(coded, decoded, 7, P1_FRAME_LEN_FM, 0133, 0171, 0165, puncture, 6); -} - -static int bit_errors_p1_am(int8_t *coded, uint8_t *decoded) -{ - uint8_t puncture[] = {1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1}; - return bit_errors(coded, decoded, 9, P1_FRAME_LEN_AM, 0561, 0657, 0711, puncture, 15); -} - -static int bit_errors_p3_am(int8_t *coded, uint8_t *decoded) -{ - uint8_t puncture[] = {1, 0, 1, 1, 0, 0}; - return bit_errors(coded, decoded, 9, P3_FRAME_LEN_AM, 0561, 0753, 0711, puncture, 6); -} - -static void descramble(uint8_t *buf, unsigned int length) -{ - const unsigned int width = 11; - unsigned int i, val = 0x3ff; - for (i = 0; i < length; i += 8) - { - unsigned int j; - for (j = 0; j < 8; ++j) - { - int bit = ((val >> 9) ^ val) & 1; - val |= bit << width; - val >>= 1; - buf[i + j] ^= bit; - } - } -} - -void decode_process_p1(decode_t *st) -{ - const int J = 20, B = 16, C = 36; - const int8_t v[] = { - 10, 2, 18, 6, 14, 8, 16, 0, 12, 4, - 11, 3, 19, 7, 15, 9, 17, 1, 13, 5 - }; - unsigned int i, out = 0; - for (i = 0; i < P1_FRAME_LEN_ENCODED_FM; i++) - { - int partition = v[i % J]; - int block = ((i / J) + (partition * 7)) % B; - int k = i / (J * B); - int row = (k * 11) % 32; - int column = (k * 11 + k / (32*9)) % C; - st->viterbi_p1[out++] = st->buffer_pm[(block * 32 + row) * 720 + partition * C + column]; - if ((out % 6) == 5) // depuncture, [1, 1, 1, 1, 1, 0] - st->viterbi_p1[out++] = 0; - } - - nrsc5_conv_decode_p1(st->viterbi_p1, st->scrambler_p1); - nrsc5_report_ber(st->input->radio, (float) bit_errors_p1_fm(st->viterbi_p1, st->scrambler_p1) / P1_FRAME_LEN_ENCODED_FM); - descramble(st->scrambler_p1, P1_FRAME_LEN_FM); - frame_push(&st->input->frame, st->scrambler_p1, P1_FRAME_LEN_FM); -} - -void decode_process_pids(decode_t *st) -{ - const int J = 20, B = 16, C = 36; - const int8_t v[] = { - 10, 2, 18, 6, 14, 8, 16, 0, 12, 4, - 11, 3, 19, 7, 15, 9, 17, 1, 13, 5 - }; - unsigned int i, out = 0; - for (i = 0; i < PIDS_FRAME_LEN_ENCODED_FM; i++) - { - int partition = v[i % J]; - int block = decode_get_block(st) - 1; - int k = ((i / J) % (PIDS_FRAME_LEN_ENCODED_FM / J)) + (P1_FRAME_LEN_ENCODED_FM / (J * B)); - int row = (k * 11) % 32; - int column = (k * 11 + k / (32*9)) % C; - st->viterbi_pids[out++] = st->buffer_pm[(block * 32 + row) * 720 + partition * C + column]; - if ((out % 6) == 5) // depuncture, [1, 1, 1, 1, 1, 0] - st->viterbi_pids[out++] = 0; - } - - nrsc5_conv_decode_pids(st->viterbi_pids, st->scrambler_pids); - descramble(st->scrambler_pids, PIDS_FRAME_LEN); - pids_frame_push(&st->pids, st->scrambler_pids); -} - -void decode_process_p3(decode_t *st) -{ - const unsigned int J = 4, B = 32, C = 36, M = 2, N = 147456; - const unsigned int bk_bits = 32 * C; - const unsigned int bk_adj = 32 * C - 1; - unsigned int i, out = 0; - for (i = 0; i < P3_FRAME_LEN_ENCODED_FM; i++) - { - int partition = ((st->i_p3 + 2 * (M / 4)) / M) % J; - unsigned int pti = (st->pt_p3[partition])++; - int block = (pti + (partition * 7) - (bk_adj * (pti / bk_bits))) % B; - int row = ((11 * pti) % bk_bits) / C; - int column = (pti * 11) % C; - st->viterbi_p3[out++] = st->internal_p3[(block * 32 + row) * 144 + partition * C + column]; - if ((out % 6) == 1 || (out % 6) == 4) // depuncture, [1, 0, 1, 1, 0, 1] - st->viterbi_p3[out++] = 0; - - st->internal_p3[st->i_p3] = st->buffer_px1[i]; - (st->i_p3)++; - } - if (st->ready_p3) - { - nrsc5_conv_decode_p3(st->viterbi_p3, st->scrambler_p3); - descramble(st->scrambler_p3, P3_FRAME_LEN_FM); - frame_push(&st->input->frame, st->scrambler_p3, P3_FRAME_LEN_FM); - } - if (st->i_p3 == N) - { - st->i_p3 = 0; - st->ready_p3 = 1; - } -} - -void decode_process_pids_am(decode_t *st) -{ - uint8_t il[120], iu[120]; - - /* 1012s.pdf section 10.4 */ - for (int n = 0; n < 120; n++) { - int k, p, row; - - p = n % 4; - - k = (n + (n/60) + 11) % 30; - row = (11 * (k + (k/15)) + 3) % 32; - il[n] = (st->buffer_pids_am[row*2] >> p) & 1; - - k = (n + (n/60)) % 30; - row = (11 * (k + (k/15)) + 3) % 32; - iu[n] = (st->buffer_pids_am[row*2 + 1] >> p) & 1; - } - - /* 1012s.pdf figure 10-5 */ - for (int i = 0; i < 10; i++) { - for (int j = 0; j < 12; j++) { - st->viterbi_pids[i*24 + pids_il_delay[j]] = il[i*12 + j] ? 1 : -1; - st->viterbi_pids[i*24 + pids_iu_delay[j]] = iu[i*12 + j] ? 1 : -1; - } - } - - nrsc5_conv_decode_e3(st->viterbi_pids, st->scrambler_pids, PIDS_FRAME_LEN); - descramble(st->scrambler_pids, PIDS_FRAME_LEN); - pids_frame_push(&st->pids, st->scrambler_pids); -} - -void decode_process_p1_p3_am(decode_t *st) -{ - int total_errors = 0; - - interleaver_ma1(st); - - if (st->am_diversity_wait > 0) - { - st->am_diversity_wait--; - return; - } - - for (int block = 0; block < 8; block++) - { - nrsc5_conv_decode_e1(st->viterbi_p1_am + (block * P1_FRAME_LEN_AM * 3), st->scrambler_p1_am, P1_FRAME_LEN_AM); - total_errors += bit_errors_p1_am(st->viterbi_p1_am + (block * P1_FRAME_LEN_AM * 3), st->scrambler_p1_am); - descramble(st->scrambler_p1_am, P1_FRAME_LEN_AM); - frame_push(&st->input->frame, st->scrambler_p1_am, P1_FRAME_LEN_AM); - } - nrsc5_conv_decode_e2(st->viterbi_p3_am, st->scrambler_p3_am, P3_FRAME_LEN_AM); - total_errors += bit_errors_p3_am(st->viterbi_p3_am, st->scrambler_p3_am); - descramble(st->scrambler_p3_am, P3_FRAME_LEN_AM); - frame_push(&st->input->frame, st->scrambler_p3_am, P3_FRAME_LEN_AM); - - nrsc5_report_ber(st->input->radio, (float) total_errors / (8 * P1_FRAME_LEN_ENCODED_AM + P3_FRAME_LEN_ENCODED_AM)); -} - -void decode_reset(decode_t *st) -{ - st->idx_pm = 0; - st->idx_px1 = 0; - st->idx_pu_pl_s_t = 0; - st->am_diversity_wait = 3; - st->i_p3 = 0; - st->ready_p3 = 0; - memset(st->pt_p3, 0, sizeof(unsigned int) * 4); - pids_init(&st->pids, st->input); -} - -void decode_init(decode_t *st, struct input_t *input) -{ - st->input = input; - decode_reset(st); -} +/* + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include "conv.h" +#include "decode.h" +#include "input.h" +#include "pids.h" +#include "private.h" + +/* 1012s.pdf figure 10-4 */ +static int bl_delay[] = { 2, 1, 5 }; +static int ml_delay[] = { 11, 6, 7 }; +static int bu_delay[] = { 10, 8, 9 }; +static int mu_delay[] = { 4, 3, 0 }; +static int el_delay[] = { 0, 1 }; +static int eu_delay[] = { 2, 3, 5, 4 }; + +/* 1012s.pdf figure 10-5 */ +static int pids_il_delay[] = { 0, 1, 12, 13, 6, 5, 18, 17, 11, 7, 23, 19 }; +static int pids_iu_delay[] = { 2, 4, 14, 16, 3, 8, 15, 20, 9, 10, 21, 22 }; + +static int bit_map(unsigned char matrix[PARTITION_WIDTH_AM * BLKSZ * 8], int b, int k, int p) +{ + int col = (9*k) % 25; + int row = (11*col + 16*(k/25) + 11*(k/50)) % 32; + return (matrix[PARTITION_WIDTH_AM * (b*BLKSZ + row) + col] >> p) & 1; +} + +static void interleaver_ma1(decode_t *st) +{ + int b, k, p; + for (int n = 0; n < 18000; n++) + { + b = n/2250; + k = (n + n/750 + 1) % 750; + p = n % 3; + st->bl[n] = bit_map(st->buffer_pl, b, k, p); + + b = (3*n + 3) % 8; + k = (n + n/3000 + 3) % 750; + p = 3 + (n % 3); + st->ml[DIVERSITY_DELAY_AM + n] = bit_map(st->buffer_pl, b, k, p); + + b = n/2250; + k = (n + n/750) % 750; + p = n % 3; + st->bu[n] = bit_map(st->buffer_pu, b, k, p); + + b = (3*n) % 8; + k = (n + n/3000 + 2) % 750; + p = 3 + (n % 3); + st->mu[DIVERSITY_DELAY_AM + n] = bit_map(st->buffer_pu, b, k, p); + } + for (int n = 0; n < 12000; n++) + { + b = (3*n + n/3000) % 8; + k = (n + (n/6000)) % 750; + p = n % 2; + st->el[n] = bit_map(st->buffer_t, b, k, p); + } + for (int n = 0; n < 24000; n++) + { + b = (3*n + n/3000 + 2*(n/12000)) % 8; + k = (n + (n/6000)) % 750; + p = n % 4; + st->eu[n] = bit_map(st->buffer_s, b, k, p); + } + + for (int i = 0; i < 6000; i++) + { + for (int j = 0; j < 3; j++) + { + st->p1_am[i*12 + bl_delay[j]] = st->bl[i*3 + j]; + st->p1_am[i*12 + ml_delay[j]] = st->ml[i*3 + j]; + st->p1_am[i*12 + bu_delay[j]] = st->bu[i*3 + j]; + st->p1_am[i*12 + mu_delay[j]] = st->mu[i*3 + j]; + } + for (int j = 0; j < 2; j++) + { + st->p3_am[i*6 + el_delay[j]] = st->el[i*2 + j]; + } + for (int j = 0; j < 4; j++) + { + st->p3_am[i*6 + eu_delay[j]] = st->eu[i*4 + j]; + } + } + + memmove(st->ml, st->ml + 18000, DIVERSITY_DELAY_AM); + memmove(st->mu, st->mu + 18000, DIVERSITY_DELAY_AM); + + int offset = 0; + for (int i = 0; i < 8 * P1_FRAME_LEN_AM * 3; i++) + { + switch (i % 15) + { + case 1: + case 4: + case 7: + st->viterbi_p1_am[i] = 0; + break; + default: + st->viterbi_p1_am[i] = st->p1_am[offset++] ? 1 : -1; + } + } + + offset = 0; + for (int i = 0; i < P3_FRAME_LEN_AM * 3; i++) + { + switch (i % 6) + { + case 1: + case 4: + case 5: + st->viterbi_p3_am[i] = 0; + break; + default: + st->viterbi_p3_am[i] = st->p3_am[offset++] ? 1 : -1; + } + } +} + +// calculate number of bit errors by re-encoding and comparing to the input +static int bit_errors(int8_t *coded, uint8_t *decoded, int k, int frame_len, + unsigned int g1, unsigned int g2, unsigned int g3, + uint8_t *puncture, int puncture_len) +{ + uint16_t r = 0; + unsigned int i, j, errors = 0; + + // tail biting + for (i = 0; i < (k-1); i++) + r = (r >> 1) | (decoded[frame_len - (k-1) + i] << (k-1)); + + for (i = 0, j = 0; i < frame_len; i++, j += 3) + { + // shift in new bit + r = (r >> 1) | (decoded[i] << (k-1)); + + if (puncture[j % puncture_len] && ((coded[j] > 0) != PARITY(r & g1))) + errors++; + if (puncture[(j+1) % puncture_len] && ((coded[j+1] > 0) != PARITY(r & g2))) + errors++; + if (puncture[(j+2) % puncture_len] && ((coded[j+2] > 0) != PARITY(r & g3))) + errors++; + } + + return errors; +} + +static int bit_errors_p1_fm(int8_t *coded, uint8_t *decoded) +{ + uint8_t puncture[] = {1, 1, 1, 1, 1, 0}; + return bit_errors(coded, decoded, 7, P1_FRAME_LEN_FM, 0133, 0171, 0165, puncture, 6); +} + +static int bit_errors_p1_am(int8_t *coded, uint8_t *decoded) +{ + uint8_t puncture[] = {1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1}; + return bit_errors(coded, decoded, 9, P1_FRAME_LEN_AM, 0561, 0657, 0711, puncture, 15); +} + +static int bit_errors_p3_am(int8_t *coded, uint8_t *decoded) +{ + uint8_t puncture[] = {1, 0, 1, 1, 0, 0}; + return bit_errors(coded, decoded, 9, P3_FRAME_LEN_AM, 0561, 0753, 0711, puncture, 6); +} + +static void descramble(uint8_t *buf, unsigned int length) +{ + const unsigned int width = 11; + unsigned int i, val = 0x3ff; + for (i = 0; i < length; i += 8) + { + unsigned int j; + for (j = 0; j < 8; ++j) + { + int bit = ((val >> 9) ^ val) & 1; + val |= bit << width; + val >>= 1; + buf[i + j] ^= bit; + } + } +} + +void decode_process_p1(decode_t *st) +{ + const int J = 20, B = 16, C = 36; + const int8_t v[] = { + 10, 2, 18, 6, 14, 8, 16, 0, 12, 4, + 11, 3, 19, 7, 15, 9, 17, 1, 13, 5 + }; + unsigned int i, out = 0; + for (i = 0; i < P1_FRAME_LEN_ENCODED_FM; i++) + { + int partition = v[i % J]; + int block = ((i / J) + (partition * 7)) % B; + int k = i / (J * B); + int row = (k * 11) % 32; + int column = (k * 11 + k / (32*9)) % C; + st->viterbi_p1[out++] = st->buffer_pm[(block * 32 + row) * 720 + partition * C + column]; + if ((out % 6) == 5) // depuncture, [1, 1, 1, 1, 1, 0] + st->viterbi_p1[out++] = 0; + } + + nrsc5_conv_decode_p1(st->viterbi_p1, st->scrambler_p1); + nrsc5_report_ber(st->input->radio, (float) bit_errors_p1_fm(st->viterbi_p1, st->scrambler_p1) / P1_FRAME_LEN_ENCODED_FM); + descramble(st->scrambler_p1, P1_FRAME_LEN_FM); + frame_push(&st->input->frame, st->scrambler_p1, P1_FRAME_LEN_FM); +} + +void decode_process_pids(decode_t *st) +{ + const int J = 20, B = 16, C = 36; + const int8_t v[] = { + 10, 2, 18, 6, 14, 8, 16, 0, 12, 4, + 11, 3, 19, 7, 15, 9, 17, 1, 13, 5 + }; + unsigned int i, out = 0; + for (i = 0; i < PIDS_FRAME_LEN_ENCODED_FM; i++) + { + int partition = v[i % J]; + int block = decode_get_block(st) - 1; + int k = ((i / J) % (PIDS_FRAME_LEN_ENCODED_FM / J)) + (P1_FRAME_LEN_ENCODED_FM / (J * B)); + int row = (k * 11) % 32; + int column = (k * 11 + k / (32*9)) % C; + st->viterbi_pids[out++] = st->buffer_pm[(block * 32 + row) * 720 + partition * C + column]; + if ((out % 6) == 5) // depuncture, [1, 1, 1, 1, 1, 0] + st->viterbi_pids[out++] = 0; + } + + nrsc5_conv_decode_pids(st->viterbi_pids, st->scrambler_pids); + descramble(st->scrambler_pids, PIDS_FRAME_LEN); + pids_frame_push(&st->pids, st->scrambler_pids); +} + +void decode_process_p3(decode_t *st) +{ + const unsigned int J = 4, B = 32, C = 36, M = 2, N = 147456; + const unsigned int bk_bits = 32 * C; + const unsigned int bk_adj = 32 * C - 1; + unsigned int i, out = 0; + for (i = 0; i < P3_FRAME_LEN_ENCODED_FM; i++) + { + int partition = ((st->i_p3 + 2 * (M / 4)) / M) % J; + unsigned int pti = (st->pt_p3[partition])++; + int block = (pti + (partition * 7) - (bk_adj * (pti / bk_bits))) % B; + int row = ((11 * pti) % bk_bits) / C; + int column = (pti * 11) % C; + st->viterbi_p3[out++] = st->internal_p3[(block * 32 + row) * 144 + partition * C + column]; + if ((out % 6) == 1 || (out % 6) == 4) // depuncture, [1, 0, 1, 1, 0, 1] + st->viterbi_p3[out++] = 0; + + st->internal_p3[st->i_p3] = st->buffer_px1[i]; + (st->i_p3)++; + } + if (st->ready_p3) + { + nrsc5_conv_decode_p3(st->viterbi_p3, st->scrambler_p3); + descramble(st->scrambler_p3, P3_FRAME_LEN_FM); + frame_push(&st->input->frame, st->scrambler_p3, P3_FRAME_LEN_FM); + } + if (st->i_p3 == N) + { + st->i_p3 = 0; + st->ready_p3 = 1; + } +} + +void decode_process_pids_am(decode_t *st) +{ + uint8_t il[120], iu[120]; + + /* 1012s.pdf section 10.4 */ + for (int n = 0; n < 120; n++) { + int k, p, row; + + p = n % 4; + + k = (n + (n/60) + 11) % 30; + row = (11 * (k + (k/15)) + 3) % 32; + il[n] = (st->buffer_pids_am[row*2] >> p) & 1; + + k = (n + (n/60)) % 30; + row = (11 * (k + (k/15)) + 3) % 32; + iu[n] = (st->buffer_pids_am[row*2 + 1] >> p) & 1; + } + + /* 1012s.pdf figure 10-5 */ + for (int i = 0; i < 10; i++) { + for (int j = 0; j < 12; j++) { + st->viterbi_pids[i*24 + pids_il_delay[j]] = il[i*12 + j] ? 1 : -1; + st->viterbi_pids[i*24 + pids_iu_delay[j]] = iu[i*12 + j] ? 1 : -1; + } + } + + nrsc5_conv_decode_e3(st->viterbi_pids, st->scrambler_pids, PIDS_FRAME_LEN); + descramble(st->scrambler_pids, PIDS_FRAME_LEN); + pids_frame_push(&st->pids, st->scrambler_pids); +} + +void decode_process_p1_p3_am(decode_t *st) +{ + int total_errors = 0; + + interleaver_ma1(st); + + if (st->am_diversity_wait > 0) + { + st->am_diversity_wait--; + return; + } + + for (int block = 0; block < 8; block++) + { + nrsc5_conv_decode_e1(st->viterbi_p1_am + (block * P1_FRAME_LEN_AM * 3), st->scrambler_p1_am, P1_FRAME_LEN_AM); + total_errors += bit_errors_p1_am(st->viterbi_p1_am + (block * P1_FRAME_LEN_AM * 3), st->scrambler_p1_am); + descramble(st->scrambler_p1_am, P1_FRAME_LEN_AM); + frame_push(&st->input->frame, st->scrambler_p1_am, P1_FRAME_LEN_AM); + } + nrsc5_conv_decode_e2(st->viterbi_p3_am, st->scrambler_p3_am, P3_FRAME_LEN_AM); + total_errors += bit_errors_p3_am(st->viterbi_p3_am, st->scrambler_p3_am); + descramble(st->scrambler_p3_am, P3_FRAME_LEN_AM); + frame_push(&st->input->frame, st->scrambler_p3_am, P3_FRAME_LEN_AM); + + nrsc5_report_ber(st->input->radio, (float) total_errors / (8 * P1_FRAME_LEN_ENCODED_AM + P3_FRAME_LEN_ENCODED_AM)); +} + +void decode_reset(decode_t *st) +{ + st->idx_pm = 0; + st->idx_px1 = 0; + st->idx_pu_pl_s_t = 0; + st->am_diversity_wait = 3; + st->i_p3 = 0; + st->ready_p3 = 0; + memset(st->pt_p3, 0, sizeof(unsigned int) * 4); + pids_init(&st->pids, st->input); +} + +void decode_init(decode_t *st, struct input_t *input) +{ + st->input = input; + decode_reset(st); +} diff --git a/src/hddsp/decode.h b/src/hddsp/decode.h index 6c27f4d..bb0d36a 100644 --- a/src/hddsp/decode.h +++ b/src/hddsp/decode.h @@ -1,107 +1,107 @@ -#pragma once - -#include -#include "defines.h" -#include "pids.h" - -#define DIVERSITY_DELAY_AM (18000 * 3) - -typedef struct -{ - struct input_t *input; - int8_t buffer_pm[720 * BLKSZ * 16]; - unsigned int idx_pm; - int8_t buffer_px1[144 * BLKSZ * 2]; - unsigned int idx_px1; - uint8_t buffer_pids_am[2 * BLKSZ]; - unsigned int idx_pids_am; - uint8_t buffer_pu[PARTITION_WIDTH_AM * BLKSZ * 8]; - uint8_t buffer_pl[PARTITION_WIDTH_AM * BLKSZ * 8]; - uint8_t buffer_s[PARTITION_WIDTH_AM * BLKSZ * 8]; - uint8_t buffer_t[PARTITION_WIDTH_AM * BLKSZ * 8]; - unsigned int idx_pu_pl_s_t; - unsigned int am_diversity_wait; - - uint8_t bl[18000]; - uint8_t bu[18000]; - uint8_t ml[18000 + DIVERSITY_DELAY_AM]; - uint8_t mu[18000 + DIVERSITY_DELAY_AM]; - uint8_t el[12000]; - uint8_t eu[24000]; - - int8_t viterbi_p1[P1_FRAME_LEN_FM * 3]; - uint8_t scrambler_p1[P1_FRAME_LEN_FM]; - int8_t viterbi_pids[PIDS_FRAME_LEN * 3]; - uint8_t scrambler_pids[PIDS_FRAME_LEN]; - int8_t internal_p3[P3_FRAME_LEN_FM * 32]; - unsigned int i_p3; - int ready_p3; - unsigned int pt_p3[4]; - int8_t viterbi_p3[P3_FRAME_LEN_FM * 3]; - uint8_t scrambler_p3[P3_FRAME_LEN_FM]; - - uint8_t p1_am[8 * P1_FRAME_LEN_ENCODED_AM]; - int8_t viterbi_p1_am[8 * P1_FRAME_LEN_AM * 3]; - uint8_t scrambler_p1_am[P1_FRAME_LEN_AM]; - uint8_t p3_am[P3_FRAME_LEN_ENCODED_AM]; - int8_t viterbi_p3_am[P3_FRAME_LEN_AM * 3]; - uint8_t scrambler_p3_am[P3_FRAME_LEN_AM]; - - pids_t pids; -} decode_t; - -void decode_process_p1(decode_t *st); -void decode_process_pids(decode_t *st); -void decode_process_p3(decode_t *st); -void decode_process_pids_am(decode_t *st); -void decode_process_p1_p3_am(decode_t *st); -static inline unsigned int decode_get_block(decode_t *st) -{ - return st->idx_pm / (720 * BLKSZ); -} -static inline void decode_push_pm(decode_t *st, int8_t sbit) -{ - st->buffer_pm[st->idx_pm++] = sbit; - if (st->idx_pm % (720 * BLKSZ) == 0) - { - decode_process_pids(st); - } - if (st->idx_pm == 720 * BLKSZ * 16) - { - decode_process_p1(st); - st->idx_pm = 0; - } -} -static inline void decode_push_px1(decode_t *st, int8_t sbit) -{ - st->buffer_px1[st->idx_px1++] = sbit; - if (st->idx_px1 % (144 * BLKSZ * 2) == 0) - { - decode_process_p3(st); - st->idx_px1 = 0; - } -} -static inline void decode_push_pids(decode_t *st, uint8_t sym) -{ - st->buffer_pids_am[st->idx_pids_am++] = sym; - if (st->idx_pids_am == 2 * BLKSZ) - { - decode_process_pids_am(st); - st->idx_pids_am = 0; - } -} -static inline void decode_push_pl_pu_s_t(decode_t *st, uint8_t sym_pl, uint8_t sym_pu, uint8_t sym_s, uint8_t sym_t) -{ - st->buffer_pl[st->idx_pu_pl_s_t] = sym_pl; - st->buffer_pu[st->idx_pu_pl_s_t] = sym_pu; - st->buffer_s[st->idx_pu_pl_s_t] = sym_s; - st->buffer_t[st->idx_pu_pl_s_t] = sym_t; - st->idx_pu_pl_s_t++; - if (st->idx_pu_pl_s_t == PARTITION_WIDTH_AM * BLKSZ * 8) - { - decode_process_p1_p3_am(st); - st->idx_pu_pl_s_t = 0; - } -} -void decode_reset(decode_t *st); -void decode_init(decode_t *st, struct input_t *input); +#pragma once + +#include +#include "defines.h" +#include "pids.h" + +#define DIVERSITY_DELAY_AM (18000 * 3) + +typedef struct +{ + struct input_t *input; + int8_t buffer_pm[720 * BLKSZ * 16]; + unsigned int idx_pm; + int8_t buffer_px1[144 * BLKSZ * 2]; + unsigned int idx_px1; + uint8_t buffer_pids_am[2 * BLKSZ]; + unsigned int idx_pids_am; + uint8_t buffer_pu[PARTITION_WIDTH_AM * BLKSZ * 8]; + uint8_t buffer_pl[PARTITION_WIDTH_AM * BLKSZ * 8]; + uint8_t buffer_s[PARTITION_WIDTH_AM * BLKSZ * 8]; + uint8_t buffer_t[PARTITION_WIDTH_AM * BLKSZ * 8]; + unsigned int idx_pu_pl_s_t; + unsigned int am_diversity_wait; + + uint8_t bl[18000]; + uint8_t bu[18000]; + uint8_t ml[18000 + DIVERSITY_DELAY_AM]; + uint8_t mu[18000 + DIVERSITY_DELAY_AM]; + uint8_t el[12000]; + uint8_t eu[24000]; + + int8_t viterbi_p1[P1_FRAME_LEN_FM * 3]; + uint8_t scrambler_p1[P1_FRAME_LEN_FM]; + int8_t viterbi_pids[PIDS_FRAME_LEN * 3]; + uint8_t scrambler_pids[PIDS_FRAME_LEN]; + int8_t internal_p3[P3_FRAME_LEN_FM * 32]; + unsigned int i_p3; + int ready_p3; + unsigned int pt_p3[4]; + int8_t viterbi_p3[P3_FRAME_LEN_FM * 3]; + uint8_t scrambler_p3[P3_FRAME_LEN_FM]; + + uint8_t p1_am[8 * P1_FRAME_LEN_ENCODED_AM]; + int8_t viterbi_p1_am[8 * P1_FRAME_LEN_AM * 3]; + uint8_t scrambler_p1_am[P1_FRAME_LEN_AM]; + uint8_t p3_am[P3_FRAME_LEN_ENCODED_AM]; + int8_t viterbi_p3_am[P3_FRAME_LEN_AM * 3]; + uint8_t scrambler_p3_am[P3_FRAME_LEN_AM]; + + pids_t pids; +} decode_t; + +void decode_process_p1(decode_t *st); +void decode_process_pids(decode_t *st); +void decode_process_p3(decode_t *st); +void decode_process_pids_am(decode_t *st); +void decode_process_p1_p3_am(decode_t *st); +static inline unsigned int decode_get_block(decode_t *st) +{ + return st->idx_pm / (720 * BLKSZ); +} +static inline void decode_push_pm(decode_t *st, int8_t sbit) +{ + st->buffer_pm[st->idx_pm++] = sbit; + if (st->idx_pm % (720 * BLKSZ) == 0) + { + decode_process_pids(st); + } + if (st->idx_pm == 720 * BLKSZ * 16) + { + decode_process_p1(st); + st->idx_pm = 0; + } +} +static inline void decode_push_px1(decode_t *st, int8_t sbit) +{ + st->buffer_px1[st->idx_px1++] = sbit; + if (st->idx_px1 % (144 * BLKSZ * 2) == 0) + { + decode_process_p3(st); + st->idx_px1 = 0; + } +} +static inline void decode_push_pids(decode_t *st, uint8_t sym) +{ + st->buffer_pids_am[st->idx_pids_am++] = sym; + if (st->idx_pids_am == 2 * BLKSZ) + { + decode_process_pids_am(st); + st->idx_pids_am = 0; + } +} +static inline void decode_push_pl_pu_s_t(decode_t *st, uint8_t sym_pl, uint8_t sym_pu, uint8_t sym_s, uint8_t sym_t) +{ + st->buffer_pl[st->idx_pu_pl_s_t] = sym_pl; + st->buffer_pu[st->idx_pu_pl_s_t] = sym_pu; + st->buffer_s[st->idx_pu_pl_s_t] = sym_s; + st->buffer_t[st->idx_pu_pl_s_t] = sym_t; + st->idx_pu_pl_s_t++; + if (st->idx_pu_pl_s_t == PARTITION_WIDTH_AM * BLKSZ * 8) + { + decode_process_p1_p3_am(st); + st->idx_pu_pl_s_t = 0; + } +} +void decode_reset(decode_t *st); +void decode_init(decode_t *st, struct input_t *input); diff --git a/src/hddsp/defines.h b/src/hddsp/defines.h index f1bc826..72ff6b5 100644 --- a/src/hddsp/defines.h +++ b/src/hddsp/defines.h @@ -1,259 +1,259 @@ -#pragma once - -#include "config.h" - -#include -#include -#include -#include -#include - -#ifdef _MSC_VER -#include -#endif - -// Sample rate before decimation -#define SAMPLE_RATE 1488375 -// FFT length in samples -#define FFT_FM 2048 -#define FFT_AM 256 -// cyclic preflex length in samples -#define CP_FM 112 -#define CP_AM 14 -#define FFTCP_FM (FFT_FM + CP_FM) -#define FFTCP_AM (FFT_AM + CP_AM) -// OFDM symbols per L1 block -#define BLKSZ 32 -// symbols processed by each invocation of acquire_process -#define ACQUIRE_SYMBOLS (BLKSZ * 2) -// index of first lower sideband subcarrier -#define LB_START ((FFT_FM / 2) - 546) -// index of last upper sideband subcarrier -#define UB_END ((FFT_FM / 2) + 546) -// index of AM carrier -#define CENTER_AM (FFT_AM / 2) -// indexes of AM subcarriers -#define REF_INDEX_AM 1 -#define PIDS_1_INDEX_AM 27 -#define PIDS_2_INDEX_AM 53 -#define TERTIARY_INDEX_AM 2 -#define SECONDARY_INDEX_AM 28 -#define PRIMARY_INDEX_AM 57 -#define MAX_INDEX_AM 81 -// bits per P1 frame -#define P1_FRAME_LEN_FM 146176 -#define P1_FRAME_LEN_AM 3750 -// bits per encoded P1 frame -#define P1_FRAME_LEN_ENCODED_FM (P1_FRAME_LEN_FM * 5 / 2) -#define P1_FRAME_LEN_ENCODED_AM (P1_FRAME_LEN_AM * 12 / 5) -// bits per PIDS frame -#define PIDS_FRAME_LEN 80 -// bits per encoded PIDS frame -#define PIDS_FRAME_LEN_ENCODED_FM (PIDS_FRAME_LEN * 5 / 2) -#define PIDS_FRAME_LEN_ENCODED_AM (PIDS_FRAME_LEN * 3) -// bits per P3 frame -#define P3_FRAME_LEN_FM 4608 -#define P3_FRAME_LEN_AM 24000 -// bits per encoded P3 frame -#define P3_FRAME_LEN_ENCODED_FM (P3_FRAME_LEN_FM * 2) -#define P3_FRAME_LEN_ENCODED_AM (P3_FRAME_LEN_AM * 3 / 2) -// bits per L2 PCI -#define PCI_LEN 24 -// bytes per L2 PDU (max) -#define MAX_PDU_LEN ((P1_FRAME_LEN_FM - PCI_LEN) / 8) -// bytes per L2 PDU in P1 frame (AM) -#define P1_PDU_LEN_AM 466 -// number of programs (max) -#define MAX_PROGRAMS 8 -// number of streams per program (max) -#define MAX_STREAMS 4 -// number of subcarriers per AM partition -#define PARTITION_WIDTH_AM 25 - -#define log_debug(...) \ - do { if (LIBRARY_DEBUG_LEVEL <= 1) { fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); } } while (0) -#define log_info(...) \ - do { if (LIBRARY_DEBUG_LEVEL <= 2) { fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); } } while (0) -#define log_warn(...) \ - do { if (LIBRARY_DEBUG_LEVEL <= 3) { fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); } } while (0) -#define log_error(...) \ - do { if (LIBRARY_DEBUG_LEVEL <= 4) { fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); } } while (0) - -#define U8_F(x) ( (((float)(x)) - 127) / 128 ) -#define U8_Q15(x) ( ((int16_t)(x) - 127) * 64 ) - -#ifdef _MSC_VER -typedef _Fcomplex fcomplex_t; -#else -typedef float complex fcomplex_t; -#endif - -#ifndef HAVE_CMPLXF -#ifdef _MSC_VER -#define CMPLXF(x,y) _FCbuild(x,y) -#elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7)) -#define CMPLXF(x,y) __builtin_complex((float)(x), (float)(y)) -#elif defined(HAVE_IMAGINARY_I) -#define CMPLXF(x,y) ((fcomplex_t)((float)(x) + _Imaginary_I * (float)(y))) -#elif defined(HAVE_COMPLEX_I) -#define CMPLXF(x,y) ((fcomplex_t)((float)(x) + _Complex_I * (float)(y))) -#endif -#endif - -#ifdef _MSC_VER -#define CMPLXFSET(x) _FCbuild(x,0) -#else -#define CMPLXFSET(x) x -#endif - -#ifdef _MSC_VER -#define CMPLXFADD(x,y) _FCbuild(x._Val[0] + y._Val[0], x._Val[1] + y._Val[1]) -#else -#define CMPLXFADD(x,y) x + y -#endif - -#ifdef _MSC_VER -#define CMPLXFSUB(x,y) _FCbuild(x._Val[0] - y._Val[0], x._Val[1] - y._Val[1]) -#else -#define CMPLXFSUB(x,y) x - y -#endif - -#ifdef _MSC_VER -#define CMPLXFNEG(x) _FCbuild(-x._Val[0], -x._Val[1]) -#else -#define CMPLXFNEG(x) -x -#endif - -#ifdef _MSC_VER -#define CMPLXFMUL(x,y) _FCmulcc(x,y) -#else -#define CMPLXFMUL(x,y) x * y -#endif - -#ifdef _MSC_VER -#define CMPLXFMULF(x,y) _FCmulcr(x,y) -#else -#define CMPLXFMULF(x,y) x * y -#endif - -#ifdef _MSC_VER -static _Fcomplex _FCdivcc(_Fcomplex lhs, _Fcomplex rhs) -{ - float re = lhs._Val[0]; - float im = lhs._Val[1]; - const float rhs_re = rhs._Val[0]; - const float rhs_im = rhs._Val[1]; - - // - // Logic taken from C++ standard library std::complex<> division operation - // TODO: clean up - // - - if(isnan(rhs_re) || isnan(rhs_im)) { // set NaN result - re = NAN; - im = re; - } - else if((rhs_im < 0 ? -rhs_im : +rhs_im) - < (rhs_re < 0 ? -rhs_re : +rhs_re)) { // |_Right.imag()| < |_Right.real()| - float wr = rhs_im / rhs_re; - float wd = rhs_re + wr * rhs_im; - - if(isnan(wd) || wd == 0) { // set NaN result - re = NAN; - im = re; - } - else { // compute representable result - float tmp = (re + im * wr) / wd; - im = (im - re * wr) / wd; - re = tmp; - } - } - else if(rhs_im == 0) { // set NaN result - re = NAN; - im = re; - } - else { // 0 < |_Right.real()| <= |_Right.imag()| - float wr = rhs_re / rhs_im; - float wd = rhs_im + wr * rhs_re; - - if(isnan(wd) || wd == 0) { // set NaN result - re = NAN; - im = re; - } - else { // compute representable result - float tmp = (re * wr + im) / wd; - im = (im * wr - re) / wd; - re = tmp; - } - } - - return _FCbuild(re, im); -} -#define CMPLXFDIV(x,y) _FCdivcc(x, y) -#else -#define CMPLXFDIV(x,y) x / y -#endif - -#ifdef _MSC_VER -static inline _Fcomplex _FCdivcr(_Fcomplex lhs, float rhs) -{ - return (isnan(rhs))? _FCbuild(NAN, NAN) : _FCbuild(lhs._Val[0] / rhs, lhs._Val[1] / rhs); -} -#define CMPLXFDIVF(x,y) _FCdivcr(x, y) -#else -#define CMPLXFDIVF(x,y) x / y -#endif - -#ifdef _MSC_VER -#define PARITY(x) ((__popcnt16(x) & 0x1) == 0x1) -#else -#define PARITY(x) __builtin_parity(x) -#endif - -typedef struct { - int16_t r, i; -} cint16_t; - -static inline cint16_t cf_to_cq15(fcomplex_t x) -{ - cint16_t cq15; - cq15.r = crealf(x) * 32767.0f; - cq15.i = cimagf(x) * 32767.0f; - return cq15; -} - -static inline fcomplex_t cq15_to_cf(cint16_t cq15) -{ - return CMPLXF((float)cq15.r / 32767.0f, (float)cq15.i / 32767.0f); -} - -static inline fcomplex_t cq15_to_cf_conj(cint16_t cq15) -{ - return CMPLXF((float)cq15.r / 32767.0f, (float)cq15.i / -32767.0f); -} - -#ifndef _MSC_VER -static inline float normf(fcomplex_t v) -{ - float realf = crealf(v); - float imagf = cimagf(v); - return realf * realf + imagf * imagf; -} -#endif - -static inline void fftshift(fcomplex_t *x, unsigned int size) -{ - int i, h = size / 2; - for (i = 0; i < h; i += 4) - { - fcomplex_t t1 = x[i], t2 = x[i+1], t3 = x[i+2], t4 = x[i+3]; - x[i] = x[i + h]; - x[i+1] = x[i+1 + h]; - x[i+2] = x[i+2 + h]; - x[i+3] = x[i+3 + h]; - x[i + h] = t1; - x[i+1 + h] = t2; - x[i+2 + h] = t3; - x[i+3 + h] = t4; - } -} +#pragma once + +#include "config.h" + +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#include +#endif + +// Sample rate before decimation +#define SAMPLE_RATE 1488375 +// FFT length in samples +#define FFT_FM 2048 +#define FFT_AM 256 +// cyclic preflex length in samples +#define CP_FM 112 +#define CP_AM 14 +#define FFTCP_FM (FFT_FM + CP_FM) +#define FFTCP_AM (FFT_AM + CP_AM) +// OFDM symbols per L1 block +#define BLKSZ 32 +// symbols processed by each invocation of acquire_process +#define ACQUIRE_SYMBOLS (BLKSZ * 2) +// index of first lower sideband subcarrier +#define LB_START ((FFT_FM / 2) - 546) +// index of last upper sideband subcarrier +#define UB_END ((FFT_FM / 2) + 546) +// index of AM carrier +#define CENTER_AM (FFT_AM / 2) +// indexes of AM subcarriers +#define REF_INDEX_AM 1 +#define PIDS_1_INDEX_AM 27 +#define PIDS_2_INDEX_AM 53 +#define TERTIARY_INDEX_AM 2 +#define SECONDARY_INDEX_AM 28 +#define PRIMARY_INDEX_AM 57 +#define MAX_INDEX_AM 81 +// bits per P1 frame +#define P1_FRAME_LEN_FM 146176 +#define P1_FRAME_LEN_AM 3750 +// bits per encoded P1 frame +#define P1_FRAME_LEN_ENCODED_FM (P1_FRAME_LEN_FM * 5 / 2) +#define P1_FRAME_LEN_ENCODED_AM (P1_FRAME_LEN_AM * 12 / 5) +// bits per PIDS frame +#define PIDS_FRAME_LEN 80 +// bits per encoded PIDS frame +#define PIDS_FRAME_LEN_ENCODED_FM (PIDS_FRAME_LEN * 5 / 2) +#define PIDS_FRAME_LEN_ENCODED_AM (PIDS_FRAME_LEN * 3) +// bits per P3 frame +#define P3_FRAME_LEN_FM 4608 +#define P3_FRAME_LEN_AM 24000 +// bits per encoded P3 frame +#define P3_FRAME_LEN_ENCODED_FM (P3_FRAME_LEN_FM * 2) +#define P3_FRAME_LEN_ENCODED_AM (P3_FRAME_LEN_AM * 3 / 2) +// bits per L2 PCI +#define PCI_LEN 24 +// bytes per L2 PDU (max) +#define MAX_PDU_LEN ((P1_FRAME_LEN_FM - PCI_LEN) / 8) +// bytes per L2 PDU in P1 frame (AM) +#define P1_PDU_LEN_AM 466 +// number of programs (max) +#define MAX_PROGRAMS 8 +// number of streams per program (max) +#define MAX_STREAMS 4 +// number of subcarriers per AM partition +#define PARTITION_WIDTH_AM 25 + +#define log_debug(...) \ + do { if (LIBRARY_DEBUG_LEVEL <= 1) { fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); } } while (0) +#define log_info(...) \ + do { if (LIBRARY_DEBUG_LEVEL <= 2) { fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); } } while (0) +#define log_warn(...) \ + do { if (LIBRARY_DEBUG_LEVEL <= 3) { fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); } } while (0) +#define log_error(...) \ + do { if (LIBRARY_DEBUG_LEVEL <= 4) { fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); } } while (0) + +#define U8_F(x) ( (((float)(x)) - 127) / 128 ) +#define U8_Q15(x) ( ((int16_t)(x) - 127) * 64 ) + +#ifdef _MSC_VER +typedef _Fcomplex fcomplex_t; +#else +typedef float complex fcomplex_t; +#endif + +#ifndef HAVE_CMPLXF +#ifdef _MSC_VER +#define CMPLXF(x,y) _FCbuild(x,y) +#elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7)) +#define CMPLXF(x,y) __builtin_complex((float)(x), (float)(y)) +#elif defined(HAVE_IMAGINARY_I) +#define CMPLXF(x,y) ((fcomplex_t)((float)(x) + _Imaginary_I * (float)(y))) +#elif defined(HAVE_COMPLEX_I) +#define CMPLXF(x,y) ((fcomplex_t)((float)(x) + _Complex_I * (float)(y))) +#endif +#endif + +#ifdef _MSC_VER +#define CMPLXFSET(x) _FCbuild(x,0) +#else +#define CMPLXFSET(x) x +#endif + +#ifdef _MSC_VER +#define CMPLXFADD(x,y) _FCbuild(x._Val[0] + y._Val[0], x._Val[1] + y._Val[1]) +#else +#define CMPLXFADD(x,y) x + y +#endif + +#ifdef _MSC_VER +#define CMPLXFSUB(x,y) _FCbuild(x._Val[0] - y._Val[0], x._Val[1] - y._Val[1]) +#else +#define CMPLXFSUB(x,y) x - y +#endif + +#ifdef _MSC_VER +#define CMPLXFNEG(x) _FCbuild(-x._Val[0], -x._Val[1]) +#else +#define CMPLXFNEG(x) -x +#endif + +#ifdef _MSC_VER +#define CMPLXFMUL(x,y) _FCmulcc(x,y) +#else +#define CMPLXFMUL(x,y) x * y +#endif + +#ifdef _MSC_VER +#define CMPLXFMULF(x,y) _FCmulcr(x,y) +#else +#define CMPLXFMULF(x,y) x * y +#endif + +#ifdef _MSC_VER +static _Fcomplex _FCdivcc(_Fcomplex lhs, _Fcomplex rhs) +{ + float re = lhs._Val[0]; + float im = lhs._Val[1]; + const float rhs_re = rhs._Val[0]; + const float rhs_im = rhs._Val[1]; + + // + // Logic taken from C++ standard library std::complex<> division operation + // TODO: clean up + // + + if(isnan(rhs_re) || isnan(rhs_im)) { // set NaN result + re = NAN; + im = re; + } + else if((rhs_im < 0 ? -rhs_im : +rhs_im) + < (rhs_re < 0 ? -rhs_re : +rhs_re)) { // |_Right.imag()| < |_Right.real()| + float wr = rhs_im / rhs_re; + float wd = rhs_re + wr * rhs_im; + + if(isnan(wd) || wd == 0) { // set NaN result + re = NAN; + im = re; + } + else { // compute representable result + float tmp = (re + im * wr) / wd; + im = (im - re * wr) / wd; + re = tmp; + } + } + else if(rhs_im == 0) { // set NaN result + re = NAN; + im = re; + } + else { // 0 < |_Right.real()| <= |_Right.imag()| + float wr = rhs_re / rhs_im; + float wd = rhs_im + wr * rhs_re; + + if(isnan(wd) || wd == 0) { // set NaN result + re = NAN; + im = re; + } + else { // compute representable result + float tmp = (re * wr + im) / wd; + im = (im * wr - re) / wd; + re = tmp; + } + } + + return _FCbuild(re, im); +} +#define CMPLXFDIV(x,y) _FCdivcc(x, y) +#else +#define CMPLXFDIV(x,y) x / y +#endif + +#ifdef _MSC_VER +static inline _Fcomplex _FCdivcr(_Fcomplex lhs, float rhs) +{ + return (isnan(rhs))? _FCbuild(NAN, NAN) : _FCbuild(lhs._Val[0] / rhs, lhs._Val[1] / rhs); +} +#define CMPLXFDIVF(x,y) _FCdivcr(x, y) +#else +#define CMPLXFDIVF(x,y) x / y +#endif + +#ifdef _MSC_VER +#define PARITY(x) ((__popcnt16(x) & 0x1) == 0x1) +#else +#define PARITY(x) __builtin_parity(x) +#endif + +typedef struct { + int16_t r, i; +} cint16_t; + +static inline cint16_t cf_to_cq15(fcomplex_t x) +{ + cint16_t cq15; + cq15.r = crealf(x) * 32767.0f; + cq15.i = cimagf(x) * 32767.0f; + return cq15; +} + +static inline fcomplex_t cq15_to_cf(cint16_t cq15) +{ + return CMPLXF((float)cq15.r / 32767.0f, (float)cq15.i / 32767.0f); +} + +static inline fcomplex_t cq15_to_cf_conj(cint16_t cq15) +{ + return CMPLXF((float)cq15.r / 32767.0f, (float)cq15.i / -32767.0f); +} + +#ifndef _MSC_VER +static inline float normf(fcomplex_t v) +{ + float realf = crealf(v); + float imagf = cimagf(v); + return realf * realf + imagf * imagf; +} +#endif + +static inline void fftshift(fcomplex_t *x, unsigned int size) +{ + int i, h = size / 2; + for (i = 0; i < h; i += 4) + { + fcomplex_t t1 = x[i], t2 = x[i+1], t3 = x[i+2], t4 = x[i+3]; + x[i] = x[i + h]; + x[i+1] = x[i+1 + h]; + x[i+2] = x[i+2 + h]; + x[i+3] = x[i+3 + h]; + x[i + h] = t1; + x[i+1 + h] = t2; + x[i+2 + h] = t3; + x[i+3 + h] = t4; + } +} diff --git a/src/hddsp/firdecim_q15.c b/src/hddsp/firdecim_q15.c index bbf04a7..f959c39 100644 --- a/src/hddsp/firdecim_q15.c +++ b/src/hddsp/firdecim_q15.c @@ -1,158 +1,158 @@ -#include "config.h" - -#include -#include - -#ifdef HAVE_NEON -#include -#endif - -#ifdef HAVE_SSE2 -#include -#endif - -#include "firdecim_q15.h" - -#define WINDOW_SIZE 2048 - -firdecim_q15 firdecim_q15_create(const float * taps, unsigned int ntaps) -{ - firdecim_q15 q; - - q = malloc(sizeof(*q)); - q->ntaps = (ntaps == 32) ? 32 : 15; - q->taps = malloc(sizeof(int16_t) * ntaps * 2); - q->window = calloc(sizeof(cint16_t), WINDOW_SIZE); - firdecim_q15_reset(q); - - // reverse order so we can push into the window - // duplicate for neon - for (unsigned int i = 0; i < ntaps; ++i) - { - q->taps[i*2] = taps[ntaps - 1 - i] * 32767.0f; - q->taps[i*2+1] = taps[ntaps - 1 - i] * 32767.0f; - } - - return q; -} - -void firdecim_q15_free(firdecim_q15 q) -{ - free(q->taps); - free(q->window); - free(q); -} - -void firdecim_q15_reset(firdecim_q15 q) -{ - q->idx = q->ntaps - 1; -} - -static void push(firdecim_q15 q, cint16_t x) -{ - if (q->idx == WINDOW_SIZE) - { - for (unsigned int i = 0; i < q->ntaps - 1; i++) - q->window[i] = q->window[q->idx - q->ntaps + 1 + i]; - q->idx = q->ntaps - 1; - } - q->window[q->idx++] = x; -} - -#ifdef HAVE_NEON -static cint16_t dotprod_32(cint16_t *a, int16_t *b) -{ - int16x8_t s1 = vqdmulhq_s16(vld1q_s16((int16_t *)&a[0]), vld1q_s16(&b[0*2])); - int16x8_t s2 = vqdmulhq_s16(vld1q_s16((int16_t *)&a[4]), vld1q_s16(&b[4*2])); - int16x8_t s3 = vqdmulhq_s16(vld1q_s16((int16_t *)&a[8]), vld1q_s16(&b[8*2])); - int16x8_t s4 = vqdmulhq_s16(vld1q_s16((int16_t *)&a[12]), vld1q_s16(&b[12*2])); - int16x8_t sum = vqaddq_s16(vqaddq_s16(s1, s2), vqaddq_s16(s3, s4)); - - s1 = vqdmulhq_s16(vld1q_s16((int16_t *)&a[16]), vld1q_s16(&b[16*2])); - s2 = vqdmulhq_s16(vld1q_s16((int16_t *)&a[20]), vld1q_s16(&b[20*2])); - s3 = vqdmulhq_s16(vld1q_s16((int16_t *)&a[24]), vld1q_s16(&b[24*2])); - s4 = vqdmulhq_s16(vld1q_s16((int16_t *)&a[28]), vld1q_s16(&b[28*2])); - sum = vqaddq_s16(vqaddq_s16(s1, s2), sum); - sum = vqaddq_s16(vqaddq_s16(s3, s4), sum); - - int16x4x2_t sum2 = vuzp_s16(vget_high_s16(sum), vget_low_s16(sum)); - int16x4_t sum3 = vpadd_s16(sum2.val[0], sum2.val[1]); - sum3 = vpadd_s16(sum3, sum3); - - cint16_t result[2]; - vst1_s16((int16_t*)&result, sum3); - - return result[0]; -} -#else -static cint16_t dotprod_32(cint16_t *a, int16_t *b) -{ - cint16_t sum = { 0 }; - int i; - - for (i = 1; i < 16; i++) - { - sum.r += ((a[i].r + a[32-i].r) * b[i * 2]) >> 15; - sum.i += ((a[i].i + a[32-i].i) * b[i * 2]) >> 15; - } - sum.r += (a[i].r * b[i * 2]) >> 15; - sum.i += (a[i].i * b[i * 2]) >> 15; - - return sum; -} -#endif - -#ifdef HAVE_NEON -static cint16_t dotprod_halfband_4(cint16_t *a, int16_t *b) -{ - cint16_t pairs[4]; - int i; - - for (i = 0; i < 7; i += 2) - { - pairs[i/2].r = a[i].r + a[14-i].r; - pairs[i/2].i = a[i].i + a[14-i].i; - } - - int16x8_t prod = vqdmulhq_s16(vld1q_s16((int16_t *)pairs), vld1q_s16(b)); - int16x4x2_t prod2 = vuzp_s16(vget_high_s16(prod), vget_low_s16(prod)); - int16x4_t sum = vpadd_s16(prod2.val[0], prod2.val[1]); - sum = vpadd_s16(sum, sum); - - cint16_t result[2]; - vst1_s16((int16_t*)&result, sum); - - result[0].r += a[7].r; - result[0].i += a[7].i; - return result[0]; -} -#else -static cint16_t dotprod_halfband_4(cint16_t *a, int16_t *b) -{ - cint16_t sum = { 0 }; - int i; - - for (i = 0; i < 7; i += 2) - { - sum.r += ((a[i].r + a[14-i].r) * b[i]) >> 15; - sum.i += ((a[i].i + a[14-i].i) * b[i]) >> 15; - } - sum.r += a[7].r; - sum.i += a[7].i; - - return sum; -} -#endif - -void fir_q15_execute(firdecim_q15 q, const cint16_t *x, cint16_t *y) -{ - push(q, x[0]); - *y = dotprod_32(&q->window[q->idx - q->ntaps], q->taps); -} - -void halfband_q15_execute(firdecim_q15 q, const cint16_t *x, cint16_t *y) -{ - push(q, x[0]); - *y = dotprod_halfband_4(&q->window[q->idx - q->ntaps], q->taps); - push(q, x[1]); -} +#include "config.h" + +#include +#include + +#ifdef HAVE_NEON +#include +#endif + +#ifdef HAVE_SSE2 +#include +#endif + +#include "firdecim_q15.h" + +#define WINDOW_SIZE 2048 + +firdecim_q15 firdecim_q15_create(const float * taps, unsigned int ntaps) +{ + firdecim_q15 q; + + q = malloc(sizeof(*q)); + q->ntaps = (ntaps == 32) ? 32 : 15; + q->taps = malloc(sizeof(int16_t) * ntaps * 2); + q->window = calloc(sizeof(cint16_t), WINDOW_SIZE); + firdecim_q15_reset(q); + + // reverse order so we can push into the window + // duplicate for neon + for (unsigned int i = 0; i < ntaps; ++i) + { + q->taps[i*2] = taps[ntaps - 1 - i] * 32767.0f; + q->taps[i*2+1] = taps[ntaps - 1 - i] * 32767.0f; + } + + return q; +} + +void firdecim_q15_free(firdecim_q15 q) +{ + free(q->taps); + free(q->window); + free(q); +} + +void firdecim_q15_reset(firdecim_q15 q) +{ + q->idx = q->ntaps - 1; +} + +static void push(firdecim_q15 q, cint16_t x) +{ + if (q->idx == WINDOW_SIZE) + { + for (unsigned int i = 0; i < q->ntaps - 1; i++) + q->window[i] = q->window[q->idx - q->ntaps + 1 + i]; + q->idx = q->ntaps - 1; + } + q->window[q->idx++] = x; +} + +#ifdef HAVE_NEON +static cint16_t dotprod_32(cint16_t *a, int16_t *b) +{ + int16x8_t s1 = vqdmulhq_s16(vld1q_s16((int16_t *)&a[0]), vld1q_s16(&b[0*2])); + int16x8_t s2 = vqdmulhq_s16(vld1q_s16((int16_t *)&a[4]), vld1q_s16(&b[4*2])); + int16x8_t s3 = vqdmulhq_s16(vld1q_s16((int16_t *)&a[8]), vld1q_s16(&b[8*2])); + int16x8_t s4 = vqdmulhq_s16(vld1q_s16((int16_t *)&a[12]), vld1q_s16(&b[12*2])); + int16x8_t sum = vqaddq_s16(vqaddq_s16(s1, s2), vqaddq_s16(s3, s4)); + + s1 = vqdmulhq_s16(vld1q_s16((int16_t *)&a[16]), vld1q_s16(&b[16*2])); + s2 = vqdmulhq_s16(vld1q_s16((int16_t *)&a[20]), vld1q_s16(&b[20*2])); + s3 = vqdmulhq_s16(vld1q_s16((int16_t *)&a[24]), vld1q_s16(&b[24*2])); + s4 = vqdmulhq_s16(vld1q_s16((int16_t *)&a[28]), vld1q_s16(&b[28*2])); + sum = vqaddq_s16(vqaddq_s16(s1, s2), sum); + sum = vqaddq_s16(vqaddq_s16(s3, s4), sum); + + int16x4x2_t sum2 = vuzp_s16(vget_high_s16(sum), vget_low_s16(sum)); + int16x4_t sum3 = vpadd_s16(sum2.val[0], sum2.val[1]); + sum3 = vpadd_s16(sum3, sum3); + + cint16_t result[2]; + vst1_s16((int16_t*)&result, sum3); + + return result[0]; +} +#else +static cint16_t dotprod_32(cint16_t *a, int16_t *b) +{ + cint16_t sum = { 0 }; + int i; + + for (i = 1; i < 16; i++) + { + sum.r += ((a[i].r + a[32-i].r) * b[i * 2]) >> 15; + sum.i += ((a[i].i + a[32-i].i) * b[i * 2]) >> 15; + } + sum.r += (a[i].r * b[i * 2]) >> 15; + sum.i += (a[i].i * b[i * 2]) >> 15; + + return sum; +} +#endif + +#ifdef HAVE_NEON +static cint16_t dotprod_halfband_4(cint16_t *a, int16_t *b) +{ + cint16_t pairs[4]; + int i; + + for (i = 0; i < 7; i += 2) + { + pairs[i/2].r = a[i].r + a[14-i].r; + pairs[i/2].i = a[i].i + a[14-i].i; + } + + int16x8_t prod = vqdmulhq_s16(vld1q_s16((int16_t *)pairs), vld1q_s16(b)); + int16x4x2_t prod2 = vuzp_s16(vget_high_s16(prod), vget_low_s16(prod)); + int16x4_t sum = vpadd_s16(prod2.val[0], prod2.val[1]); + sum = vpadd_s16(sum, sum); + + cint16_t result[2]; + vst1_s16((int16_t*)&result, sum); + + result[0].r += a[7].r; + result[0].i += a[7].i; + return result[0]; +} +#else +static cint16_t dotprod_halfband_4(cint16_t *a, int16_t *b) +{ + cint16_t sum = { 0 }; + int i; + + for (i = 0; i < 7; i += 2) + { + sum.r += ((a[i].r + a[14-i].r) * b[i]) >> 15; + sum.i += ((a[i].i + a[14-i].i) * b[i]) >> 15; + } + sum.r += a[7].r; + sum.i += a[7].i; + + return sum; +} +#endif + +void fir_q15_execute(firdecim_q15 q, const cint16_t *x, cint16_t *y) +{ + push(q, x[0]); + *y = dotprod_32(&q->window[q->idx - q->ntaps], q->taps); +} + +void halfband_q15_execute(firdecim_q15 q, const cint16_t *x, cint16_t *y) +{ + push(q, x[0]); + *y = dotprod_halfband_4(&q->window[q->idx - q->ntaps], q->taps); + push(q, x[1]); +} diff --git a/src/hddsp/firdecim_q15.h b/src/hddsp/firdecim_q15.h index 7442f80..18ec41b 100644 --- a/src/hddsp/firdecim_q15.h +++ b/src/hddsp/firdecim_q15.h @@ -1,16 +1,16 @@ -#pragma once - -#include "defines.h" - -typedef struct _firdecim_q15 { - int16_t* taps; - unsigned int ntaps; - cint16_t* window; - unsigned int idx; -} *firdecim_q15; - -firdecim_q15 firdecim_q15_create(const float * taps, unsigned int ntaps); -void firdecim_q15_free(firdecim_q15); -void firdecim_q15_reset(firdecim_q15); -void fir_q15_execute(firdecim_q15 q, const cint16_t *x, cint16_t *y); -void halfband_q15_execute(firdecim_q15 q, const cint16_t *x, cint16_t *y); +#pragma once + +#include "defines.h" + +typedef struct _firdecim_q15 { + int16_t* taps; + unsigned int ntaps; + cint16_t* window; + unsigned int idx; +} *firdecim_q15; + +firdecim_q15 firdecim_q15_create(const float * taps, unsigned int ntaps); +void firdecim_q15_free(firdecim_q15); +void firdecim_q15_reset(firdecim_q15); +void fir_q15_execute(firdecim_q15 q, const cint16_t *x, cint16_t *y); +void halfband_q15_execute(firdecim_q15 q, const cint16_t *x, cint16_t *y); diff --git a/src/hddsp/frame.c b/src/hddsp/frame.c index fbe97ac..f0e5c91 100644 --- a/src/hddsp/frame.c +++ b/src/hddsp/frame.c @@ -1,650 +1,650 @@ -/* - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include - -#include "defines.h" -#include "frame.h" -#include "input.h" -#include "rs_char.h" - -#define PCI_AUDIO 0x38D8D3 -#define PCI_AUDIO_FIXED 0xE3634C -#define PCI_AUDIO_FIXED_OPP 0x8D8D33 - -#define MAX_AUDIO_PACKETS 64 - -typedef struct -{ - unsigned int codec; - unsigned int stream_id; - unsigned int pdu_seq; - unsigned int blend_control; - unsigned int per_stream_delay; - unsigned int common_delay; - unsigned int latency; - unsigned int pfirst; - unsigned int plast; - unsigned int seq; - unsigned int nop; - unsigned int hef; - unsigned int la_location; -} frame_header_t; - -typedef struct -{ - unsigned int class_ind; - unsigned int prog_num; - unsigned int pdu_len; - unsigned int prog_type; - unsigned int access; - unsigned int applied_services; - unsigned int pdu_marker; -} hef_t; - -static const uint8_t crc8_tab[] = { - 0, 0x31, 0x62, 0x53, 0xC4, 0xF5, 0xA6, 0x97, 0xB9, - 0x88, 0xDB, 0xEA, 0x7D, 0x4C, 0x1F, 0x2E, 0x43, 0x72, - 0x21, 0x10, 0x87, 0xB6, 0xE5, 0xD4, 0xFA, 0xCB, 0x98, - 0xA9, 0x3E, 0xF, 0x5C, 0x6D, 0x86, 0xB7, 0xE4, 0xD5, - 0x42, 0x73, 0x20, 0x11, 0x3F, 0xE, 0x5D, 0x6C, 0xFB, - 0xCA, 0x99, 0xA8, 0xC5, 0xF4, 0xA7, 0x96, 1, 0x30, - 0x63, 0x52, 0x7C, 0x4D, 0x1E, 0x2F, 0xB8, 0x89, 0xDA, - 0xEB, 0x3D, 0xC, 0x5F, 0x6E, 0xF9, 0xC8, 0x9B, 0xAA, - 0x84, 0xB5, 0xE6, 0xD7, 0x40, 0x71, 0x22, 0x13, 0x7E, - 0x4F, 0x1C, 0x2D, 0xBA, 0x8B, 0xD8, 0xE9, 0xC7, 0xF6, - 0xA5, 0x94, 3, 0x32, 0x61, 0x50, 0xBB, 0x8A, 0xD9, - 0xE8, 0x7F, 0x4E, 0x1D, 0x2C, 2, 0x33, 0x60, 0x51, - 0xC6, 0xF7, 0xA4, 0x95, 0xF8, 0xC9, 0x9A, 0xAB, 0x3C, - 0xD, 0x5E, 0x6F, 0x41, 0x70, 0x23, 0x12, 0x85, 0xB4, - 0xE7, 0xD6, 0x7A, 0x4B, 0x18, 0x29, 0xBE, 0x8F, 0xDC, - 0xED, 0xC3, 0xF2, 0xA1, 0x90, 7, 0x36, 0x65, 0x54, - 0x39, 8, 0x5B, 0x6A, 0xFD, 0xCC, 0x9F, 0xAE, 0x80, - 0xB1, 0xE2, 0xD3, 0x44, 0x75, 0x26, 0x17, 0xFC, 0xCD, - 0x9E, 0xAF, 0x38, 9, 0x5A, 0x6B, 0x45, 0x74, 0x27, - 0x16, 0x81, 0xB0, 0xE3, 0xD2, 0xBF, 0x8E, 0xDD, 0xEC, - 0x7B, 0x4A, 0x19, 0x28, 6, 0x37, 0x64, 0x55, 0xC2, - 0xF3, 0xA0, 0x91, 0x47, 0x76, 0x25, 0x14, 0x83, 0xB2, - 0xE1, 0xD0, 0xFE, 0xCF, 0x9C, 0xAD, 0x3A, 0xB, 0x58, - 0x69, 4, 0x35, 0x66, 0x57, 0xC0, 0xF1, 0xA2, 0x93, - 0xBD, 0x8C, 0xDF, 0xEE, 0x79, 0x48, 0x1B, 0x2A, 0xC1, - 0xF0, 0xA3, 0x92, 5, 0x34, 0x67, 0x56, 0x78, 0x49, - 0x1A, 0x2B, 0xBC, 0x8D, 0xDE, 0xEF, 0x82, 0xB3, 0xE0, - 0xD1, 0x46, 0x77, 0x24, 0x15, 0x3B, 0xA, 0x59, 0x68, - 0xFF, 0xCE, 0x9D, 0xAC -}; - -static const uint16_t fcs_tab[] = { - 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, - 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, - 0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e, - 0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, - 0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd, - 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5, - 0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c, - 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974, - 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb, - 0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, - 0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a, - 0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72, - 0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, - 0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1, - 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738, - 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70, - 0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7, - 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff, - 0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, - 0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, - 0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, - 0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, - 0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134, - 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c, - 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3, - 0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb, - 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232, - 0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, - 0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1, - 0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9, - 0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, - 0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78 -}; - -/* Good final FCS value */ -#define VALIDFCS16 0xf0b8 - -static uint8_t crc8(const uint8_t *pkt, unsigned int cnt) -{ - unsigned int i, crc = 0xFF; - for (i = 0; i < cnt; ++i) - crc = crc8_tab[crc ^ pkt[i]]; - return crc; -} - -static uint16_t fcs16(const uint8_t *cp, int len) -{ - uint16_t crc = 0xFFFF; - while (len--) - crc = (crc >> 8) ^ fcs_tab[(crc ^ *cp++) & 0xFF]; - return (crc); -} - -static int has_fixed(frame_t *st) -{ - return (st->pci & 0xFFFFFC) == (PCI_AUDIO_FIXED & 0xFFFFFC) - || (st->pci & 0xFFFFFC) == (PCI_AUDIO_FIXED & 0xFFFFFC); -} - -static int fix_header(frame_t *st, uint8_t *buf) -{ - uint8_t hdr[RS_BLOCK_LEN]; - int i, corrections; - - memset(hdr, 0, RS_BLOCK_LEN-RS_CODEWORD_LEN); - for (i = 0; i < RS_CODEWORD_LEN; i++) - hdr[RS_BLOCK_LEN-i-1] = buf[i]; - - corrections = decode_rs_char(st->rs_dec, hdr, NULL, 0); - - if (corrections == -1) - return 0; - - for (i = 0; i < RS_BLOCK_LEN-RS_CODEWORD_LEN; i++) - if (hdr[i] != 0) - return 0; - - if (corrections > 0) - log_debug("RS corrected %d symbols", corrections); - - for (i = 0; i < RS_CODEWORD_LEN; i++) - buf[i] = hdr[RS_BLOCK_LEN-i-1]; - return 1; -} - -static void parse_header(uint8_t *buf, frame_header_t *hdr) -{ - hdr->codec = buf[8] & 0xf; - hdr->stream_id = (buf[8] >> 4) & 0x3; - hdr->pdu_seq = (buf[8] >> 6) | ((buf[9] & 1) << 2); - hdr->blend_control = (buf[9] >> 1) & 0x3; - hdr->per_stream_delay = buf[9] >> 3; - hdr->common_delay = buf[10] & 0x3f; - hdr->latency = (buf[10] >> 6) | ((buf[11] & 1) << 2); - hdr->pfirst = (buf[11] >> 1) & 1; - hdr->plast = (buf[11] >> 2) & 1; - hdr->seq = (buf[11] >> 3) | ((buf[12] & 1) << 5); - hdr->nop = (buf[12] >> 1) & 0x3f; - hdr->hef = buf[12] >> 7; - hdr->la_location = buf[13]; -} - -static unsigned int parse_hef(uint8_t *buf, unsigned int length, hef_t *hef) -{ - uint8_t *byte = buf, *end = buf + length; - - do - { - if (byte >= end) return length; - - switch ((*byte >> 4) & 0x7) - { - case 0: - hef->class_ind = *byte & 0xf; - break; - case 1: - hef->prog_num = (*byte >> 1) & 0x7; - if (*byte & 0x1) - { - if (byte + 2 >= end) return length; - byte++; - hef->pdu_len = (*byte & 0x7f) << 7; - byte++; - hef->pdu_len |= (*byte & 0x7f); - } - break; - case 2: - if (byte + 1 >= end) return length; - hef->access = (*byte >> 3) & 0x1; - hef->prog_type = (*byte & 0x1) << 7; - byte++; - hef->prog_type |= (*byte & 0x7f); - break; - case 3: - if (*byte & 0x8) - { - if (byte + 4 >= end) return length; - byte += 4; - } - else - { - if (byte + 3 >= end) return length; - byte += 3; - } - break; - case 4: - if (*byte & 0x8) - { - if (byte + 3 >= end) return length; - hef->applied_services = (*byte & 0x7); - byte++; - hef->pdu_marker = (*byte & 0x7f) << 14; - byte++; - hef->pdu_marker |= (*byte & 0x7f) << 7; - byte++; - hef->pdu_marker |= (*byte & 0x7f); - } - else - { - if (byte + 1 >= end) return length; - byte++; - } - break; - default: - log_debug("unknown header expansion ID"); - } - } while (*(byte++) & 0x80); - - return byte - buf; -} - -static unsigned int calc_lc_bits(frame_header_t *hdr) -{ - switch(hdr->codec) - { - case 0: - return 16; - case 1: - case 2: - case 3: - if (hdr->stream_id == 0) - return 12; - else - return 16; - case 10: - case 13: - return 12; - default: - log_warn("unknown codec field (%d)", hdr->codec); - return 16; - } -} - -static unsigned int parse_location(uint8_t *buf, unsigned int lc_bits, unsigned int i) -{ - if (lc_bits == 16) - return (buf[2*i + 1] << 8) | buf[2*i]; - else - { - if (i % 2 == 0) - return ((buf[i/2*3 + 1] & 0xf) << 8) | buf[i/2*3]; - else - return (buf[i/2*3 + 2] << 4) | (buf[i/2*3 + 1] >> 4); - } -} - -static int unescape_hdlc(uint8_t *data, int length) -{ - uint8_t *p = data; - - for (int i = 0; i < length; i++) - { - if (data[i] == 0x7D) - *p++ = data[++i] | 0x20; - else - *p++ = data[i]; - } - - return p - data; -} - -static void aas_push(frame_t *st, uint8_t* psd, unsigned int length) -{ - length = unescape_hdlc(psd, length); - - if (length == 0) - { - // empty frames are used as padding - } - else if (fcs16(psd, length) != VALIDFCS16) - { - log_info("psd crc mismatch"); - } - else if (psd[0] != 0x21) - { - log_warn("unknown AAS protocol %x", psd[0]); - } - else - { - // remove protocol and fcs fields - input_aas_push(st->input, psd + 1, length - 3); - } -} - -static void parse_hdlc(frame_t *st, void (*process)(frame_t *, uint8_t *, unsigned int), uint8_t *buffer, int *bufidx, int bufsz, uint8_t *input, size_t inlen) -{ - for (size_t i = 0; i < inlen; i++) - { - uint8_t byte = input[i]; - if (byte == 0x7E) - { - if (*bufidx >= 0) - process(st, buffer, *bufidx); - *bufidx = 0; - } - else if (*bufidx >= 0) - { - if (*bufidx == bufsz) - { - log_error("HDLC buffer overflow"); - *bufidx = -1; - continue; - } - buffer[(*bufidx)++] = byte; - } - } -} - -static void process_fixed_ccc(frame_t *st, uint8_t *buf, unsigned int buflen) -{ - buflen = unescape_hdlc(buf, buflen); - - // padding - if (buflen == 0) - return; - - // ignore new CCC packets (XXX they shouldn't change) - if (st->fixed_ready) - return; - - if (fcs16(buf, buflen) != VALIDFCS16) - { - log_info("bad CCC checksum"); - return; - } - - for (unsigned int i = 0; i < 4; i++) - { - fixed_subchannel_t *subch = &st->subchannel[i]; - subch->mode = 0; - subch->length = 0; - - if (5 + i * 4 <= buflen) - { - uint16_t mode = buf[1 + i * 4] | (buf[2 + i * 4] << 8); - uint16_t length = buf[3 + i * 4] | (buf[4 + i * 4] << 8); - log_info("Subchannel %d: mode=%d, length=%d", i, mode, length); - - if (mode == 0) - { - subch->mode = mode; - subch->length = length; - subch->block_idx = 0; - subch->idx = -1; - } - else - { - log_warn("Subchannel mode %04X not supported", mode); - } - } - } - - st->fixed_ready = 1; -} - -/* FIXME: We only support mode=0 (no FEC, no interleaving) */ -static void process_fixed_block(frame_t *st, int i) -{ - fixed_subchannel_t *subch = &st->subchannel[i]; - parse_hdlc(st, aas_push, subch->data, &subch->idx, MAX_AAS_LEN, &subch->blocks[4], 255); -} - -static size_t process_fixed_data(frame_t *st, size_t length) -{ - static const uint8_t bbm[] = { 0x7D, 0x3A, 0xE2, 0x42 }; - uint8_t *p = &st->buffer[length - 1]; - - if (st->sync_count < 2) - { - unsigned int width = (*p & 0xF) * 2; - if (st->sync_width == width) - st->sync_count++; - else - st->sync_count = 0; - st->sync_width = width; - - if (st->sync_count < 2) - return p - st->buffer; - } - - p -= st->sync_width; - parse_hdlc(st, process_fixed_ccc, st->ccc_buf, &st->ccc_idx, sizeof(st->ccc_buf), p, st->sync_width); - - // wait until we have subchannel information - if (!st->fixed_ready) - return p - st->buffer; - - for (int i = 3; i >= 0; i--) - { - fixed_subchannel_t *subch = &st->subchannel[i]; - int length = subch->length; - - if (length == 0) - continue; - - p -= length; - for (int j = 0; j < length; j++) - { - subch->blocks[subch->block_idx++] = p[j]; - if (subch->block_idx == 4 && memcmp(subch->blocks, bbm, sizeof(bbm)) != 0) - { - // mis-aligned, skip a byte - memmove(subch->blocks, subch->blocks + 1, 3); - subch->block_idx--; - } - - if (subch->block_idx == 255 + 4) - { - // we have a complete block, deinterleave and process - process_fixed_block(st, i); - subch->block_idx = 0; - } - } - } - - return p - st->buffer; -} - -void frame_process(frame_t *st, size_t length) -{ - unsigned int offset = 0; - unsigned int audio_end = length; - - if (has_fixed(st)) - audio_end = process_fixed_data(st, length); - - while (offset < audio_end - RS_CODEWORD_LEN) - { - unsigned int start = offset; - unsigned int j, lc_bits, loc_bytes, prog; - unsigned short locations[MAX_AUDIO_PACKETS]; - frame_header_t hdr = {0}; - hef_t hef = {0}; - - if (!fix_header(st, st->buffer + offset)) - { - // go back to coarse sync if we fail to decode any audio packets in a P1 frame - if ((length == MAX_PDU_LEN || length == P1_PDU_LEN_AM) && offset == 0) - input_set_sync_state(st->input, SYNC_STATE_NONE); - return; - } - - parse_header(st->buffer + offset, &hdr); - offset += 14; - lc_bits = calc_lc_bits(&hdr); - loc_bytes = ((lc_bits * hdr.nop) + 4) / 8; - if (start + hdr.la_location + 1 < offset + loc_bytes || start + hdr.la_location >= audio_end) - return; - - for (j = 0; j < hdr.nop; j++) - { - locations[j] = parse_location(st->buffer + offset, lc_bits, j); - if (j == 0 && locations[j] <= hdr.la_location) return; - if (j > 0 && locations[j] <= locations[j-1]) return; - if (start + locations[j] >= audio_end) return; - } - offset += loc_bytes; - - if (hdr.hef) - offset += parse_hef(st->buffer + offset, audio_end - offset, &hef); - prog = hef.prog_num; - - parse_hdlc(st, aas_push, st->psd_buf[prog], &st->psd_idx[prog], MAX_AAS_LEN, st->buffer + offset, start + hdr.la_location + 1 - offset); - offset = start + hdr.la_location + 1; - - for (j = 0; j < hdr.nop; ++j) - { - unsigned int cnt = start + locations[j] - offset; - uint8_t crc = crc8(st->buffer + offset, cnt + 1); - - if (crc != 0) - log_warn("crc mismatch!"); - - if (j == 0 && hdr.pfirst) - { - unsigned int idx = st->pdu_idx[prog][hdr.stream_id]; - if (idx) - { - if (crc == 0) - { - memcpy(&st->pdu[prog][hdr.stream_id][idx], st->buffer + offset, cnt); - input_pdu_push(st->input, st->pdu[prog][hdr.stream_id], cnt + idx, prog, hdr.stream_id); - } - st->pdu_idx[prog][hdr.stream_id] = 0; - } - else - { - log_debug("ignoring partial pdu"); - } - } - else if (j == hdr.nop - 1 && hdr.plast) - { - if (crc == 0) - { - memcpy(st->pdu[prog][hdr.stream_id], st->buffer + offset, cnt); - st->pdu_idx[prog][hdr.stream_id] = cnt; - } - } - else - { - if (crc == 0) - { - input_pdu_push(st->input, st->buffer + offset, cnt, prog, hdr.stream_id); - } - } - - offset += cnt + 1; - } - } - -} - -void frame_push(frame_t *st, uint8_t *bits, size_t length) -{ - unsigned int start, offset, pci_len; - unsigned int i, j = 0, h = 0, header = 0, val = 0; - uint8_t *ptr = st->buffer; - - switch (length) - { - case P1_FRAME_LEN_FM: - start = P1_FRAME_LEN_FM - 30000; - offset = 1248; - pci_len = 24; - break; - case P3_FRAME_LEN_FM: - start = 120; - offset = 184; - pci_len = 24; - break; - case P1_FRAME_LEN_AM: - start = 120; - offset = 160; - pci_len = 22; - break; - case P3_FRAME_LEN_AM: - start = 120; - offset = 992; - pci_len = 24; - break; - default: - log_error("Unknown frame length: %zu", length); - } - - for (i = 0; i < length; ++i) - { - // swap bit order - unsigned int byte_start = (i>>3)<<3; - unsigned int byte_len = (length - byte_start < 8) ? length - byte_start : 8; - uint8_t bit = bits[byte_start + byte_len - 1 - (i & 7)]; - - if (i >= start && ((i - start) % offset) == 0 && h < pci_len) - { - header |= bit << (23 - h); - ++h; - } - else - { - val |= bit << (7 - j); - if (++j == 8) - { - *ptr++ = val; - val = 0; - j = 0; - } - } - } - - st->pci = header; - frame_process(st, ptr - st->buffer); -} - -void frame_reset(frame_t *st) -{ - st->pci = 0; - for (int prog = 0; prog < MAX_PROGRAMS; prog++) - { - for (int stream_id = 0; stream_id < MAX_STREAMS; stream_id++) - { - st->pdu_idx[prog][stream_id] = 0; - } - st->psd_idx[prog] = -1; - } - - st->fixed_ready = 0; - st->sync_width = 0; - st->sync_count = 0; - st->ccc_idx = -1; -} - -void frame_init(frame_t *st, input_t *input) -{ - st->input = input; - st->rs_dec = init_rs_char(8, 0x11d, 1, 1, 8); - frame_reset(st); -} - -void frame_free(frame_t *st) -{ - free_rs_char(st->rs_dec); -} +/* + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include "defines.h" +#include "frame.h" +#include "input.h" +#include "rs_char.h" + +#define PCI_AUDIO 0x38D8D3 +#define PCI_AUDIO_FIXED 0xE3634C +#define PCI_AUDIO_FIXED_OPP 0x8D8D33 + +#define MAX_AUDIO_PACKETS 64 + +typedef struct +{ + unsigned int codec; + unsigned int stream_id; + unsigned int pdu_seq; + unsigned int blend_control; + unsigned int per_stream_delay; + unsigned int common_delay; + unsigned int latency; + unsigned int pfirst; + unsigned int plast; + unsigned int seq; + unsigned int nop; + unsigned int hef; + unsigned int la_location; +} frame_header_t; + +typedef struct +{ + unsigned int class_ind; + unsigned int prog_num; + unsigned int pdu_len; + unsigned int prog_type; + unsigned int access; + unsigned int applied_services; + unsigned int pdu_marker; +} hef_t; + +static const uint8_t crc8_tab[] = { + 0, 0x31, 0x62, 0x53, 0xC4, 0xF5, 0xA6, 0x97, 0xB9, + 0x88, 0xDB, 0xEA, 0x7D, 0x4C, 0x1F, 0x2E, 0x43, 0x72, + 0x21, 0x10, 0x87, 0xB6, 0xE5, 0xD4, 0xFA, 0xCB, 0x98, + 0xA9, 0x3E, 0xF, 0x5C, 0x6D, 0x86, 0xB7, 0xE4, 0xD5, + 0x42, 0x73, 0x20, 0x11, 0x3F, 0xE, 0x5D, 0x6C, 0xFB, + 0xCA, 0x99, 0xA8, 0xC5, 0xF4, 0xA7, 0x96, 1, 0x30, + 0x63, 0x52, 0x7C, 0x4D, 0x1E, 0x2F, 0xB8, 0x89, 0xDA, + 0xEB, 0x3D, 0xC, 0x5F, 0x6E, 0xF9, 0xC8, 0x9B, 0xAA, + 0x84, 0xB5, 0xE6, 0xD7, 0x40, 0x71, 0x22, 0x13, 0x7E, + 0x4F, 0x1C, 0x2D, 0xBA, 0x8B, 0xD8, 0xE9, 0xC7, 0xF6, + 0xA5, 0x94, 3, 0x32, 0x61, 0x50, 0xBB, 0x8A, 0xD9, + 0xE8, 0x7F, 0x4E, 0x1D, 0x2C, 2, 0x33, 0x60, 0x51, + 0xC6, 0xF7, 0xA4, 0x95, 0xF8, 0xC9, 0x9A, 0xAB, 0x3C, + 0xD, 0x5E, 0x6F, 0x41, 0x70, 0x23, 0x12, 0x85, 0xB4, + 0xE7, 0xD6, 0x7A, 0x4B, 0x18, 0x29, 0xBE, 0x8F, 0xDC, + 0xED, 0xC3, 0xF2, 0xA1, 0x90, 7, 0x36, 0x65, 0x54, + 0x39, 8, 0x5B, 0x6A, 0xFD, 0xCC, 0x9F, 0xAE, 0x80, + 0xB1, 0xE2, 0xD3, 0x44, 0x75, 0x26, 0x17, 0xFC, 0xCD, + 0x9E, 0xAF, 0x38, 9, 0x5A, 0x6B, 0x45, 0x74, 0x27, + 0x16, 0x81, 0xB0, 0xE3, 0xD2, 0xBF, 0x8E, 0xDD, 0xEC, + 0x7B, 0x4A, 0x19, 0x28, 6, 0x37, 0x64, 0x55, 0xC2, + 0xF3, 0xA0, 0x91, 0x47, 0x76, 0x25, 0x14, 0x83, 0xB2, + 0xE1, 0xD0, 0xFE, 0xCF, 0x9C, 0xAD, 0x3A, 0xB, 0x58, + 0x69, 4, 0x35, 0x66, 0x57, 0xC0, 0xF1, 0xA2, 0x93, + 0xBD, 0x8C, 0xDF, 0xEE, 0x79, 0x48, 0x1B, 0x2A, 0xC1, + 0xF0, 0xA3, 0x92, 5, 0x34, 0x67, 0x56, 0x78, 0x49, + 0x1A, 0x2B, 0xBC, 0x8D, 0xDE, 0xEF, 0x82, 0xB3, 0xE0, + 0xD1, 0x46, 0x77, 0x24, 0x15, 0x3B, 0xA, 0x59, 0x68, + 0xFF, 0xCE, 0x9D, 0xAC +}; + +static const uint16_t fcs_tab[] = { + 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, + 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, + 0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e, + 0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, + 0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd, + 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5, + 0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c, + 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974, + 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb, + 0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, + 0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a, + 0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72, + 0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, + 0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1, + 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738, + 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70, + 0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7, + 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff, + 0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, + 0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, + 0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, + 0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, + 0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134, + 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c, + 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3, + 0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb, + 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232, + 0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, + 0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1, + 0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9, + 0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, + 0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78 +}; + +/* Good final FCS value */ +#define VALIDFCS16 0xf0b8 + +static uint8_t crc8(const uint8_t *pkt, unsigned int cnt) +{ + unsigned int i, crc = 0xFF; + for (i = 0; i < cnt; ++i) + crc = crc8_tab[crc ^ pkt[i]]; + return crc; +} + +static uint16_t fcs16(const uint8_t *cp, int len) +{ + uint16_t crc = 0xFFFF; + while (len--) + crc = (crc >> 8) ^ fcs_tab[(crc ^ *cp++) & 0xFF]; + return (crc); +} + +static int has_fixed(frame_t *st) +{ + return (st->pci & 0xFFFFFC) == (PCI_AUDIO_FIXED & 0xFFFFFC) + || (st->pci & 0xFFFFFC) == (PCI_AUDIO_FIXED & 0xFFFFFC); +} + +static int fix_header(frame_t *st, uint8_t *buf) +{ + uint8_t hdr[RS_BLOCK_LEN]; + int i, corrections; + + memset(hdr, 0, RS_BLOCK_LEN-RS_CODEWORD_LEN); + for (i = 0; i < RS_CODEWORD_LEN; i++) + hdr[RS_BLOCK_LEN-i-1] = buf[i]; + + corrections = decode_rs_char(st->rs_dec, hdr, NULL, 0); + + if (corrections == -1) + return 0; + + for (i = 0; i < RS_BLOCK_LEN-RS_CODEWORD_LEN; i++) + if (hdr[i] != 0) + return 0; + + if (corrections > 0) + log_debug("RS corrected %d symbols", corrections); + + for (i = 0; i < RS_CODEWORD_LEN; i++) + buf[i] = hdr[RS_BLOCK_LEN-i-1]; + return 1; +} + +static void parse_header(uint8_t *buf, frame_header_t *hdr) +{ + hdr->codec = buf[8] & 0xf; + hdr->stream_id = (buf[8] >> 4) & 0x3; + hdr->pdu_seq = (buf[8] >> 6) | ((buf[9] & 1) << 2); + hdr->blend_control = (buf[9] >> 1) & 0x3; + hdr->per_stream_delay = buf[9] >> 3; + hdr->common_delay = buf[10] & 0x3f; + hdr->latency = (buf[10] >> 6) | ((buf[11] & 1) << 2); + hdr->pfirst = (buf[11] >> 1) & 1; + hdr->plast = (buf[11] >> 2) & 1; + hdr->seq = (buf[11] >> 3) | ((buf[12] & 1) << 5); + hdr->nop = (buf[12] >> 1) & 0x3f; + hdr->hef = buf[12] >> 7; + hdr->la_location = buf[13]; +} + +static unsigned int parse_hef(uint8_t *buf, unsigned int length, hef_t *hef) +{ + uint8_t *byte = buf, *end = buf + length; + + do + { + if (byte >= end) return length; + + switch ((*byte >> 4) & 0x7) + { + case 0: + hef->class_ind = *byte & 0xf; + break; + case 1: + hef->prog_num = (*byte >> 1) & 0x7; + if (*byte & 0x1) + { + if (byte + 2 >= end) return length; + byte++; + hef->pdu_len = (*byte & 0x7f) << 7; + byte++; + hef->pdu_len |= (*byte & 0x7f); + } + break; + case 2: + if (byte + 1 >= end) return length; + hef->access = (*byte >> 3) & 0x1; + hef->prog_type = (*byte & 0x1) << 7; + byte++; + hef->prog_type |= (*byte & 0x7f); + break; + case 3: + if (*byte & 0x8) + { + if (byte + 4 >= end) return length; + byte += 4; + } + else + { + if (byte + 3 >= end) return length; + byte += 3; + } + break; + case 4: + if (*byte & 0x8) + { + if (byte + 3 >= end) return length; + hef->applied_services = (*byte & 0x7); + byte++; + hef->pdu_marker = (*byte & 0x7f) << 14; + byte++; + hef->pdu_marker |= (*byte & 0x7f) << 7; + byte++; + hef->pdu_marker |= (*byte & 0x7f); + } + else + { + if (byte + 1 >= end) return length; + byte++; + } + break; + default: + log_debug("unknown header expansion ID"); + } + } while (*(byte++) & 0x80); + + return byte - buf; +} + +static unsigned int calc_lc_bits(frame_header_t *hdr) +{ + switch(hdr->codec) + { + case 0: + return 16; + case 1: + case 2: + case 3: + if (hdr->stream_id == 0) + return 12; + else + return 16; + case 10: + case 13: + return 12; + default: + log_warn("unknown codec field (%d)", hdr->codec); + return 16; + } +} + +static unsigned int parse_location(uint8_t *buf, unsigned int lc_bits, unsigned int i) +{ + if (lc_bits == 16) + return (buf[2*i + 1] << 8) | buf[2*i]; + else + { + if (i % 2 == 0) + return ((buf[i/2*3 + 1] & 0xf) << 8) | buf[i/2*3]; + else + return (buf[i/2*3 + 2] << 4) | (buf[i/2*3 + 1] >> 4); + } +} + +static int unescape_hdlc(uint8_t *data, int length) +{ + uint8_t *p = data; + + for (int i = 0; i < length; i++) + { + if (data[i] == 0x7D) + *p++ = data[++i] | 0x20; + else + *p++ = data[i]; + } + + return p - data; +} + +static void aas_push(frame_t *st, uint8_t* psd, unsigned int length) +{ + length = unescape_hdlc(psd, length); + + if (length == 0) + { + // empty frames are used as padding + } + else if (fcs16(psd, length) != VALIDFCS16) + { + log_info("psd crc mismatch"); + } + else if (psd[0] != 0x21) + { + log_warn("unknown AAS protocol %x", psd[0]); + } + else + { + // remove protocol and fcs fields + input_aas_push(st->input, psd + 1, length - 3); + } +} + +static void parse_hdlc(frame_t *st, void (*process)(frame_t *, uint8_t *, unsigned int), uint8_t *buffer, int *bufidx, int bufsz, uint8_t *input, size_t inlen) +{ + for (size_t i = 0; i < inlen; i++) + { + uint8_t byte = input[i]; + if (byte == 0x7E) + { + if (*bufidx >= 0) + process(st, buffer, *bufidx); + *bufidx = 0; + } + else if (*bufidx >= 0) + { + if (*bufidx == bufsz) + { + log_error("HDLC buffer overflow"); + *bufidx = -1; + continue; + } + buffer[(*bufidx)++] = byte; + } + } +} + +static void process_fixed_ccc(frame_t *st, uint8_t *buf, unsigned int buflen) +{ + buflen = unescape_hdlc(buf, buflen); + + // padding + if (buflen == 0) + return; + + // ignore new CCC packets (XXX they shouldn't change) + if (st->fixed_ready) + return; + + if (fcs16(buf, buflen) != VALIDFCS16) + { + log_info("bad CCC checksum"); + return; + } + + for (unsigned int i = 0; i < 4; i++) + { + fixed_subchannel_t *subch = &st->subchannel[i]; + subch->mode = 0; + subch->length = 0; + + if (5 + i * 4 <= buflen) + { + uint16_t mode = buf[1 + i * 4] | (buf[2 + i * 4] << 8); + uint16_t length = buf[3 + i * 4] | (buf[4 + i * 4] << 8); + log_info("Subchannel %d: mode=%d, length=%d", i, mode, length); + + if (mode == 0) + { + subch->mode = mode; + subch->length = length; + subch->block_idx = 0; + subch->idx = -1; + } + else + { + log_warn("Subchannel mode %04X not supported", mode); + } + } + } + + st->fixed_ready = 1; +} + +/* FIXME: We only support mode=0 (no FEC, no interleaving) */ +static void process_fixed_block(frame_t *st, int i) +{ + fixed_subchannel_t *subch = &st->subchannel[i]; + parse_hdlc(st, aas_push, subch->data, &subch->idx, MAX_AAS_LEN, &subch->blocks[4], 255); +} + +static size_t process_fixed_data(frame_t *st, size_t length) +{ + static const uint8_t bbm[] = { 0x7D, 0x3A, 0xE2, 0x42 }; + uint8_t *p = &st->buffer[length - 1]; + + if (st->sync_count < 2) + { + unsigned int width = (*p & 0xF) * 2; + if (st->sync_width == width) + st->sync_count++; + else + st->sync_count = 0; + st->sync_width = width; + + if (st->sync_count < 2) + return p - st->buffer; + } + + p -= st->sync_width; + parse_hdlc(st, process_fixed_ccc, st->ccc_buf, &st->ccc_idx, sizeof(st->ccc_buf), p, st->sync_width); + + // wait until we have subchannel information + if (!st->fixed_ready) + return p - st->buffer; + + for (int i = 3; i >= 0; i--) + { + fixed_subchannel_t *subch = &st->subchannel[i]; + int length = subch->length; + + if (length == 0) + continue; + + p -= length; + for (int j = 0; j < length; j++) + { + subch->blocks[subch->block_idx++] = p[j]; + if (subch->block_idx == 4 && memcmp(subch->blocks, bbm, sizeof(bbm)) != 0) + { + // mis-aligned, skip a byte + memmove(subch->blocks, subch->blocks + 1, 3); + subch->block_idx--; + } + + if (subch->block_idx == 255 + 4) + { + // we have a complete block, deinterleave and process + process_fixed_block(st, i); + subch->block_idx = 0; + } + } + } + + return p - st->buffer; +} + +void frame_process(frame_t *st, size_t length) +{ + unsigned int offset = 0; + unsigned int audio_end = length; + + if (has_fixed(st)) + audio_end = process_fixed_data(st, length); + + while (offset < audio_end - RS_CODEWORD_LEN) + { + unsigned int start = offset; + unsigned int j, lc_bits, loc_bytes, prog; + unsigned short locations[MAX_AUDIO_PACKETS]; + frame_header_t hdr = {0}; + hef_t hef = {0}; + + if (!fix_header(st, st->buffer + offset)) + { + // go back to coarse sync if we fail to decode any audio packets in a P1 frame + if ((length == MAX_PDU_LEN || length == P1_PDU_LEN_AM) && offset == 0) + input_set_sync_state(st->input, SYNC_STATE_NONE); + return; + } + + parse_header(st->buffer + offset, &hdr); + offset += 14; + lc_bits = calc_lc_bits(&hdr); + loc_bytes = ((lc_bits * hdr.nop) + 4) / 8; + if (start + hdr.la_location + 1 < offset + loc_bytes || start + hdr.la_location >= audio_end) + return; + + for (j = 0; j < hdr.nop; j++) + { + locations[j] = parse_location(st->buffer + offset, lc_bits, j); + if (j == 0 && locations[j] <= hdr.la_location) return; + if (j > 0 && locations[j] <= locations[j-1]) return; + if (start + locations[j] >= audio_end) return; + } + offset += loc_bytes; + + if (hdr.hef) + offset += parse_hef(st->buffer + offset, audio_end - offset, &hef); + prog = hef.prog_num; + + parse_hdlc(st, aas_push, st->psd_buf[prog], &st->psd_idx[prog], MAX_AAS_LEN, st->buffer + offset, start + hdr.la_location + 1 - offset); + offset = start + hdr.la_location + 1; + + for (j = 0; j < hdr.nop; ++j) + { + unsigned int cnt = start + locations[j] - offset; + uint8_t crc = crc8(st->buffer + offset, cnt + 1); + + if (crc != 0) + log_warn("crc mismatch!"); + + if (j == 0 && hdr.pfirst) + { + unsigned int idx = st->pdu_idx[prog][hdr.stream_id]; + if (idx) + { + if (crc == 0) + { + memcpy(&st->pdu[prog][hdr.stream_id][idx], st->buffer + offset, cnt); + input_pdu_push(st->input, st->pdu[prog][hdr.stream_id], cnt + idx, prog, hdr.stream_id); + } + st->pdu_idx[prog][hdr.stream_id] = 0; + } + else + { + log_debug("ignoring partial pdu"); + } + } + else if (j == hdr.nop - 1 && hdr.plast) + { + if (crc == 0) + { + memcpy(st->pdu[prog][hdr.stream_id], st->buffer + offset, cnt); + st->pdu_idx[prog][hdr.stream_id] = cnt; + } + } + else + { + if (crc == 0) + { + input_pdu_push(st->input, st->buffer + offset, cnt, prog, hdr.stream_id); + } + } + + offset += cnt + 1; + } + } + +} + +void frame_push(frame_t *st, uint8_t *bits, size_t length) +{ + unsigned int start, offset, pci_len; + unsigned int i, j = 0, h = 0, header = 0, val = 0; + uint8_t *ptr = st->buffer; + + switch (length) + { + case P1_FRAME_LEN_FM: + start = P1_FRAME_LEN_FM - 30000; + offset = 1248; + pci_len = 24; + break; + case P3_FRAME_LEN_FM: + start = 120; + offset = 184; + pci_len = 24; + break; + case P1_FRAME_LEN_AM: + start = 120; + offset = 160; + pci_len = 22; + break; + case P3_FRAME_LEN_AM: + start = 120; + offset = 992; + pci_len = 24; + break; + default: + log_error("Unknown frame length: %zu", length); + } + + for (i = 0; i < length; ++i) + { + // swap bit order + unsigned int byte_start = (i>>3)<<3; + unsigned int byte_len = (length - byte_start < 8) ? length - byte_start : 8; + uint8_t bit = bits[byte_start + byte_len - 1 - (i & 7)]; + + if (i >= start && ((i - start) % offset) == 0 && h < pci_len) + { + header |= bit << (23 - h); + ++h; + } + else + { + val |= bit << (7 - j); + if (++j == 8) + { + *ptr++ = val; + val = 0; + j = 0; + } + } + } + + st->pci = header; + frame_process(st, ptr - st->buffer); +} + +void frame_reset(frame_t *st) +{ + st->pci = 0; + for (int prog = 0; prog < MAX_PROGRAMS; prog++) + { + for (int stream_id = 0; stream_id < MAX_STREAMS; stream_id++) + { + st->pdu_idx[prog][stream_id] = 0; + } + st->psd_idx[prog] = -1; + } + + st->fixed_ready = 0; + st->sync_width = 0; + st->sync_count = 0; + st->ccc_idx = -1; +} + +void frame_init(frame_t *st, input_t *input) +{ + st->input = input; + st->rs_dec = init_rs_char(8, 0x11d, 1, 1, 8); + frame_reset(st); +} + +void frame_free(frame_t *st) +{ + free_rs_char(st->rs_dec); +} diff --git a/src/hddsp/frame.h b/src/hddsp/frame.h index aeef0f8..c8260e1 100644 --- a/src/hddsp/frame.h +++ b/src/hddsp/frame.h @@ -1,43 +1,43 @@ -#pragma once - -#include "defines.h" - -#define MAX_AAS_LEN 8212 -#define RS_BLOCK_LEN 255 -#define RS_CODEWORD_LEN 96 - -typedef struct -{ - uint16_t mode; - uint16_t length; - unsigned int block_idx; - uint8_t blocks[255 + 4]; - int idx; - uint8_t data[MAX_AAS_LEN]; -} fixed_subchannel_t; - -typedef struct -{ - struct input_t *input; - uint8_t buffer[MAX_PDU_LEN]; - uint8_t pdu[MAX_PROGRAMS][MAX_STREAMS][0x10000]; - unsigned int pdu_idx[MAX_PROGRAMS][MAX_STREAMS]; - unsigned int pci; - unsigned int program; - uint8_t psd_buf[MAX_PROGRAMS][MAX_AAS_LEN]; - int psd_idx[MAX_PROGRAMS]; - - unsigned int sync_width; - unsigned int sync_count; - uint8_t ccc_buf[32]; - int ccc_idx; - fixed_subchannel_t subchannel[4]; - int fixed_ready; - void *rs_dec; -} frame_t; - -void frame_push(frame_t *st, uint8_t *bits, size_t length); -void frame_reset(frame_t *st); -void frame_set_program(frame_t *st, unsigned int program); -void frame_init(frame_t *st, struct input_t *input); -void frame_free(frame_t *st); +#pragma once + +#include "defines.h" + +#define MAX_AAS_LEN 8212 +#define RS_BLOCK_LEN 255 +#define RS_CODEWORD_LEN 96 + +typedef struct +{ + uint16_t mode; + uint16_t length; + unsigned int block_idx; + uint8_t blocks[255 + 4]; + int idx; + uint8_t data[MAX_AAS_LEN]; +} fixed_subchannel_t; + +typedef struct +{ + struct input_t *input; + uint8_t buffer[MAX_PDU_LEN]; + uint8_t pdu[MAX_PROGRAMS][MAX_STREAMS][0x10000]; + unsigned int pdu_idx[MAX_PROGRAMS][MAX_STREAMS]; + unsigned int pci; + unsigned int program; + uint8_t psd_buf[MAX_PROGRAMS][MAX_AAS_LEN]; + int psd_idx[MAX_PROGRAMS]; + + unsigned int sync_width; + unsigned int sync_count; + uint8_t ccc_buf[32]; + int ccc_idx; + fixed_subchannel_t subchannel[4]; + int fixed_ready; + void *rs_dec; +} frame_t; + +void frame_push(frame_t *st, uint8_t *bits, size_t length); +void frame_reset(frame_t *st); +void frame_set_program(frame_t *st, unsigned int program); +void frame_init(frame_t *st, struct input_t *input); +void frame_free(frame_t *st); diff --git a/src/hddsp/input.c b/src/hddsp/input.c index 7d66113..f9da104 100644 --- a/src/hddsp/input.c +++ b/src/hddsp/input.c @@ -1,302 +1,302 @@ -/* - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "config.h" - -#ifdef _MSC_VER -#define _USE_MATH_DEFINES -#endif - -#include -#include -#include - -#include "defines.h" -#include "input.h" -#include "private.h" - -/* - * GNU Radio Filter Design Tool - * FIR, Low Pass, Kaiser Window - * Sample rate: 1488375 - * End of pass band: 372094 - * Start of stop band: 530000 - * Stop band attenuation: 40 - */ -static float decim_taps[] = { - 0.6062333583831787, - -0.13481467962265015, - 0.032919470220804214, - -0.00410953676328063 -}; - -static void input_push_to_acquire(input_t *st) -{ - if (st->skip) - { - if (st->skip > st->avail - st->used) - { - st->skip -= st->avail - st->used; - st->used = st->avail; - } - else - { - st->used += st->skip; - st->skip = 0; - } - } - - st->used += acquire_push(&st->acq, &st->buffer[st->used], st->avail - st->used); -} - -void input_pdu_push(input_t *st, uint8_t *pdu, unsigned int len, unsigned int program, unsigned int stream_id) -{ - output_push(st->output, pdu, len, program, stream_id); -} - -void input_set_skip(input_t *st, unsigned int skip) -{ - st->skip += skip; -} - -static void measure_snr(input_t *st, uint8_t *buf, uint32_t len) -{ - unsigned int i, j; - - // use a small FFT to calculate magnitude of frequency ranges - for (j = 0; j + SNR_FFT_LEN <= len / 2; j += SNR_FFT_LEN) - { - for (i = 0; i < SNR_FFT_LEN; i++) - st->snr_fft_in[i] = CMPLXFMULF(CMPLXF(U8_F(buf[(i+j) * 2]), U8_F(buf[(i+j) * 2 + 1])), pow(sinf(M_PI*i/(SNR_FFT_LEN-1)), 2)); - fftwf_execute(st->snr_fft); - fftshift(st->snr_fft_out, SNR_FFT_LEN); - - for (i = 0; i < SNR_FFT_LEN; i++) - st->snr_power[i] += normf(st->snr_fft_out[i]); - st->snr_cnt++; - } - - if (st->snr_cnt >= SNR_FFT_COUNT) - { - // noise bands are the frequncies near our signal - float noise = 0; - for (i = SNR_NOISE_START; i < SNR_NOISE_START + SNR_NOISE_LEN; i++) - { - noise += st->snr_power[i]; - noise += st->snr_power[SNR_FFT_LEN - i]; - } - noise /= SNR_NOISE_LEN * 2; - - // signal bands are the frequencies in our signal - float signal = 0; - for (i = SNR_SIGNAL_START; i < SNR_SIGNAL_START + SNR_SIGNAL_LEN; i++) - { - signal += st->snr_power[i]; - signal += st->snr_power[SNR_FFT_LEN - i]; - } - signal /= SNR_SIGNAL_LEN * 2; - - if (st->snr_cb(st->snr_cb_arg, signal / noise) == 0) - st->snr_cb = NULL; - - st->snr_cnt = 0; - for (i = 0; i < SNR_FFT_LEN; ++i) - st->snr_power[i] = 0; - } -} - -int input_shift(input_t *st, unsigned int cnt) -{ - if (cnt + st->avail > INPUT_BUF_LEN) - { - if (st->avail > st->used) - { - memmove(&st->buffer[0], &st->buffer[st->used], (st->avail - st->used) * sizeof(st->buffer[0])); - st->avail -= st->used; - st->used = 0; - } - else - { - st->avail = 0; - st->used = 0; - } - } - - if (cnt + st->avail > INPUT_BUF_LEN) - { - log_error("input buffer overflow!"); - return -1; - } - - return 0; -} - -void input_push(input_t *st) -{ - while (st->avail - st->used >= (st->radio->mode == NRSC5_MODE_FM ? FFTCP_FM : FFTCP_AM)) - { - input_push_to_acquire(st); - acquire_process(&st->acq); - } -} - -void input_push_cu8(input_t *st, uint8_t *buf, uint32_t len) -{ - unsigned int i; - assert(len % 4 == 0); - - if (st->snr_cb) - { - measure_snr(st, buf, len); - return; - } - - nrsc5_report_iq(st->radio, buf, len); - - if (input_shift(st, len / 4) != 0) - return; - - for (i = 0; i < len; i += 4) - { - cint16_t x[2]; - - x[0].r = U8_Q15(buf[i]); - x[0].i = U8_Q15(buf[i + 1]); - x[1].r = U8_Q15(buf[i + 2]); - x[1].i = U8_Q15(buf[i + 3]); - - if (st->radio->mode == NRSC5_MODE_FM) - { - halfband_q15_execute(st->decim[0], x, &st->buffer[st->avail++]); - } - else - { - x[0].r >>= 4; - x[0].i >>= 4; - x[1].r >>= 4; - x[1].i >>= 4; - - halfband_q15_execute(st->decim[0], x, &st->stages[0][st->offset & 1]); - if ((st->offset & 0x1) == 0x1) { - halfband_q15_execute(st->decim[1], st->stages[0], &st->stages[1][(st->offset >> 1) & 1]); - } - if ((st->offset & 0x3) == 0x3) { - halfband_q15_execute(st->decim[2], st->stages[1], &st->stages[2][(st->offset >> 2) & 1]); - } - if ((st->offset & 0x7) == 0x7) { - halfband_q15_execute(st->decim[3], st->stages[2], &st->stages[3][(st->offset >> 3) & 1]); - } - if ((st->offset & 0xf) == 0xf) { - halfband_q15_execute(st->decim[4], st->stages[3], &st->buffer[st->avail++]); - } - st->offset++; - } - } - - input_push(st); -} - -void input_push_cs16(input_t *st, int16_t *buf, uint32_t len) -{ - assert(len % 2 == 0); - - if (input_shift(st, len / 2) != 0) - return; - - memcpy(&st->buffer[st->avail], buf, len * sizeof(int16_t)); - st->avail += len / 2; - - input_push(st); -} - -void input_set_snr_callback(input_t *st, input_snr_cb_t cb, void *arg) -{ - st->snr_cb = cb; - st->snr_cb_arg = arg; -} - -void input_reset(input_t *st) -{ - st->avail = 0; - st->used = 0; - st->skip = 0; - st->offset = 0; - for (int i = 0; i < SNR_FFT_LEN; ++i) - st->snr_power[i] = 0; - st->snr_cnt = 0; - - input_set_sync_state(st, SYNC_STATE_NONE); - for (int i = 0; i < AM_DECIM_STAGES; i++) - firdecim_q15_reset(st->decim[i]); - acquire_reset(&st->acq); - decode_reset(&st->decode); - frame_reset(&st->frame); - sync_reset(&st->sync); -} - -void input_init(input_t *st, nrsc5_t *radio, output_t *output) -{ - st->radio = radio; - st->output = output; - st->snr_cb = NULL; - st->snr_cb_arg = NULL; - st->sync_state = SYNC_STATE_NONE; - - for (int i = 0; i < AM_DECIM_STAGES; i++) - st->decim[i] = firdecim_q15_create(decim_taps, sizeof(decim_taps) / sizeof(decim_taps[0])); - st->snr_fft = fftwf_plan_dft_1d(SNR_FFT_LEN, (fftwf_complex*)(st->snr_fft_in), (fftwf_complex*)st->snr_fft_out, FFTW_FORWARD, 0); - - acquire_init(&st->acq, st); - decode_init(&st->decode, st); - frame_init(&st->frame, st); - sync_init(&st->sync, st); - - input_reset(st); -} - -void input_set_mode(input_t *st) -{ - acquire_set_mode(&st->acq, st->radio->mode); - input_reset(st); -} - -void input_free(input_t *st) -{ - acquire_free(&st->acq); - frame_free(&st->frame); - - for (int i = 0; i < AM_DECIM_STAGES; i++) - firdecim_q15_free(st->decim[i]); - fftwf_destroy_plan(st->snr_fft); - fftwf_cleanup(); -} - -void input_set_sync_state(input_t *st, unsigned int new_state) -{ - if (st->sync_state == new_state) - return; - - if (st->sync_state == SYNC_STATE_FINE) - nrsc5_report_lost_sync(st->radio); - if (new_state == SYNC_STATE_FINE) - nrsc5_report_sync(st->radio); - - st->sync_state = new_state; -} - -void input_aas_push(input_t *st, uint8_t *psd, unsigned int len) -{ - output_aas_push(st->output, psd, len); -} +/* + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#ifdef _MSC_VER +#define _USE_MATH_DEFINES +#endif + +#include +#include +#include + +#include "defines.h" +#include "input.h" +#include "private.h" + +/* + * GNU Radio Filter Design Tool + * FIR, Low Pass, Kaiser Window + * Sample rate: 1488375 + * End of pass band: 372094 + * Start of stop band: 530000 + * Stop band attenuation: 40 + */ +static float decim_taps[] = { + 0.6062333583831787, + -0.13481467962265015, + 0.032919470220804214, + -0.00410953676328063 +}; + +static void input_push_to_acquire(input_t *st) +{ + if (st->skip) + { + if (st->skip > st->avail - st->used) + { + st->skip -= st->avail - st->used; + st->used = st->avail; + } + else + { + st->used += st->skip; + st->skip = 0; + } + } + + st->used += acquire_push(&st->acq, &st->buffer[st->used], st->avail - st->used); +} + +void input_pdu_push(input_t *st, uint8_t *pdu, unsigned int len, unsigned int program, unsigned int stream_id) +{ + output_push(st->output, pdu, len, program, stream_id); +} + +void input_set_skip(input_t *st, unsigned int skip) +{ + st->skip += skip; +} + +static void measure_snr(input_t *st, uint8_t *buf, uint32_t len) +{ + unsigned int i, j; + + // use a small FFT to calculate magnitude of frequency ranges + for (j = 0; j + SNR_FFT_LEN <= len / 2; j += SNR_FFT_LEN) + { + for (i = 0; i < SNR_FFT_LEN; i++) + st->snr_fft_in[i] = CMPLXFMULF(CMPLXF(U8_F(buf[(i+j) * 2]), U8_F(buf[(i+j) * 2 + 1])), pow(sinf(M_PI*i/(SNR_FFT_LEN-1)), 2)); + fftwf_execute(st->snr_fft); + fftshift(st->snr_fft_out, SNR_FFT_LEN); + + for (i = 0; i < SNR_FFT_LEN; i++) + st->snr_power[i] += normf(st->snr_fft_out[i]); + st->snr_cnt++; + } + + if (st->snr_cnt >= SNR_FFT_COUNT) + { + // noise bands are the frequncies near our signal + float noise = 0; + for (i = SNR_NOISE_START; i < SNR_NOISE_START + SNR_NOISE_LEN; i++) + { + noise += st->snr_power[i]; + noise += st->snr_power[SNR_FFT_LEN - i]; + } + noise /= SNR_NOISE_LEN * 2; + + // signal bands are the frequencies in our signal + float signal = 0; + for (i = SNR_SIGNAL_START; i < SNR_SIGNAL_START + SNR_SIGNAL_LEN; i++) + { + signal += st->snr_power[i]; + signal += st->snr_power[SNR_FFT_LEN - i]; + } + signal /= SNR_SIGNAL_LEN * 2; + + if (st->snr_cb(st->snr_cb_arg, signal / noise) == 0) + st->snr_cb = NULL; + + st->snr_cnt = 0; + for (i = 0; i < SNR_FFT_LEN; ++i) + st->snr_power[i] = 0; + } +} + +int input_shift(input_t *st, unsigned int cnt) +{ + if (cnt + st->avail > INPUT_BUF_LEN) + { + if (st->avail > st->used) + { + memmove(&st->buffer[0], &st->buffer[st->used], (st->avail - st->used) * sizeof(st->buffer[0])); + st->avail -= st->used; + st->used = 0; + } + else + { + st->avail = 0; + st->used = 0; + } + } + + if (cnt + st->avail > INPUT_BUF_LEN) + { + log_error("input buffer overflow!"); + return -1; + } + + return 0; +} + +void input_push(input_t *st) +{ + while (st->avail - st->used >= (st->radio->mode == NRSC5_MODE_FM ? FFTCP_FM : FFTCP_AM)) + { + input_push_to_acquire(st); + acquire_process(&st->acq); + } +} + +void input_push_cu8(input_t *st, uint8_t *buf, uint32_t len) +{ + unsigned int i; + assert(len % 4 == 0); + + if (st->snr_cb) + { + measure_snr(st, buf, len); + return; + } + + nrsc5_report_iq(st->radio, buf, len); + + if (input_shift(st, len / 4) != 0) + return; + + for (i = 0; i < len; i += 4) + { + cint16_t x[2]; + + x[0].r = U8_Q15(buf[i]); + x[0].i = U8_Q15(buf[i + 1]); + x[1].r = U8_Q15(buf[i + 2]); + x[1].i = U8_Q15(buf[i + 3]); + + if (st->radio->mode == NRSC5_MODE_FM) + { + halfband_q15_execute(st->decim[0], x, &st->buffer[st->avail++]); + } + else + { + x[0].r >>= 4; + x[0].i >>= 4; + x[1].r >>= 4; + x[1].i >>= 4; + + halfband_q15_execute(st->decim[0], x, &st->stages[0][st->offset & 1]); + if ((st->offset & 0x1) == 0x1) { + halfband_q15_execute(st->decim[1], st->stages[0], &st->stages[1][(st->offset >> 1) & 1]); + } + if ((st->offset & 0x3) == 0x3) { + halfband_q15_execute(st->decim[2], st->stages[1], &st->stages[2][(st->offset >> 2) & 1]); + } + if ((st->offset & 0x7) == 0x7) { + halfband_q15_execute(st->decim[3], st->stages[2], &st->stages[3][(st->offset >> 3) & 1]); + } + if ((st->offset & 0xf) == 0xf) { + halfband_q15_execute(st->decim[4], st->stages[3], &st->buffer[st->avail++]); + } + st->offset++; + } + } + + input_push(st); +} + +void input_push_cs16(input_t *st, int16_t *buf, uint32_t len) +{ + assert(len % 2 == 0); + + if (input_shift(st, len / 2) != 0) + return; + + memcpy(&st->buffer[st->avail], buf, len * sizeof(int16_t)); + st->avail += len / 2; + + input_push(st); +} + +void input_set_snr_callback(input_t *st, input_snr_cb_t cb, void *arg) +{ + st->snr_cb = cb; + st->snr_cb_arg = arg; +} + +void input_reset(input_t *st) +{ + st->avail = 0; + st->used = 0; + st->skip = 0; + st->offset = 0; + for (int i = 0; i < SNR_FFT_LEN; ++i) + st->snr_power[i] = 0; + st->snr_cnt = 0; + + input_set_sync_state(st, SYNC_STATE_NONE); + for (int i = 0; i < AM_DECIM_STAGES; i++) + firdecim_q15_reset(st->decim[i]); + acquire_reset(&st->acq); + decode_reset(&st->decode); + frame_reset(&st->frame); + sync_reset(&st->sync); +} + +void input_init(input_t *st, nrsc5_t *radio, output_t *output) +{ + st->radio = radio; + st->output = output; + st->snr_cb = NULL; + st->snr_cb_arg = NULL; + st->sync_state = SYNC_STATE_NONE; + + for (int i = 0; i < AM_DECIM_STAGES; i++) + st->decim[i] = firdecim_q15_create(decim_taps, sizeof(decim_taps) / sizeof(decim_taps[0])); + st->snr_fft = fftwf_plan_dft_1d(SNR_FFT_LEN, (fftwf_complex*)(st->snr_fft_in), (fftwf_complex*)st->snr_fft_out, FFTW_FORWARD, 0); + + acquire_init(&st->acq, st); + decode_init(&st->decode, st); + frame_init(&st->frame, st); + sync_init(&st->sync, st); + + input_reset(st); +} + +void input_set_mode(input_t *st) +{ + acquire_set_mode(&st->acq, st->radio->mode); + input_reset(st); +} + +void input_free(input_t *st) +{ + acquire_free(&st->acq); + frame_free(&st->frame); + + for (int i = 0; i < AM_DECIM_STAGES; i++) + firdecim_q15_free(st->decim[i]); + fftwf_destroy_plan(st->snr_fft); + fftwf_cleanup(); +} + +void input_set_sync_state(input_t *st, unsigned int new_state) +{ + if (st->sync_state == new_state) + return; + + if (st->sync_state == SYNC_STATE_FINE) + nrsc5_report_lost_sync(st->radio); + if (new_state == SYNC_STATE_FINE) + nrsc5_report_sync(st->radio); + + st->sync_state = new_state; +} + +void input_aas_push(input_t *st, uint8_t *psd, unsigned int len) +{ + output_aas_push(st->output, psd, len); +} diff --git a/src/hddsp/input.h b/src/hddsp/input.h index af3602c..0e0ae4c 100644 --- a/src/hddsp/input.h +++ b/src/hddsp/input.h @@ -1,65 +1,65 @@ -#pragma once - -#include -//#include - -#include "nrsc5.h" - -#include "acquire.h" -#include "decode.h" -#include "defines.h" -#include "firdecim_q15.h" -#include "frame.h" -#include "output.h" -#include "sync.h" - -#define INPUT_BUF_LEN (FFTCP_FM * 512) -#define AM_DECIM_STAGES 5 - -#define SNR_FFT_COUNT 256 -#define SNR_FFT_LEN 64 -#define SNR_NOISE_START 19 -#define SNR_NOISE_LEN 4 -#define SNR_SIGNAL_START 24 -#define SNR_SIGNAL_LEN 2 - -typedef int (*input_snr_cb_t) (void *, float); - -enum { SYNC_STATE_NONE, SYNC_STATE_COARSE, SYNC_STATE_FINE }; - -typedef struct input_t -{ - nrsc5_t *radio; - output_t *output; - - firdecim_q15 decim[AM_DECIM_STAGES]; - cint16_t stages[AM_DECIM_STAGES][2]; - cint16_t buffer[INPUT_BUF_LEN]; - unsigned int avail, used, skip, offset; - unsigned int sync_state; - - fftwf_plan snr_fft; - fcomplex_t snr_fft_in[SNR_FFT_LEN]; - fcomplex_t snr_fft_out[SNR_FFT_LEN]; - float snr_power[SNR_FFT_LEN]; - int snr_cnt; - input_snr_cb_t snr_cb; - void *snr_cb_arg; - - acquire_t acq; - decode_t decode; - frame_t frame; - sync_t sync; -} input_t; - -void input_init(input_t *st, nrsc5_t *radio, output_t *output); -void input_set_mode(input_t *st); -void input_reset(input_t *st); -void input_free(input_t *st); -void input_set_sync_state(input_t *st, unsigned int new_state); -void input_push_cu8(input_t *st, uint8_t *buf, uint32_t len); -void input_push_cs16(input_t *st, int16_t *buf, uint32_t len); -void input_set_snr_callback(input_t *st, input_snr_cb_t cb, void *); -void input_set_skip(input_t *st, unsigned int skip); -void input_pdu_push(input_t *st, uint8_t *pdu, unsigned int len, unsigned int program, unsigned int stream_id); -void input_aas_push(input_t *st, uint8_t *psd, unsigned int len); +#pragma once + +#include +//#include + +#include "nrsc5.h" + +#include "acquire.h" +#include "decode.h" +#include "defines.h" +#include "firdecim_q15.h" +#include "frame.h" +#include "output.h" +#include "sync.h" + +#define INPUT_BUF_LEN (FFTCP_FM * 512) +#define AM_DECIM_STAGES 5 + +#define SNR_FFT_COUNT 256 +#define SNR_FFT_LEN 64 +#define SNR_NOISE_START 19 +#define SNR_NOISE_LEN 4 +#define SNR_SIGNAL_START 24 +#define SNR_SIGNAL_LEN 2 + +typedef int (*input_snr_cb_t) (void *, float); + +enum { SYNC_STATE_NONE, SYNC_STATE_COARSE, SYNC_STATE_FINE }; + +typedef struct input_t +{ + nrsc5_t *radio; + output_t *output; + + firdecim_q15 decim[AM_DECIM_STAGES]; + cint16_t stages[AM_DECIM_STAGES][2]; + cint16_t buffer[INPUT_BUF_LEN]; + unsigned int avail, used, skip, offset; + unsigned int sync_state; + + fftwf_plan snr_fft; + fcomplex_t snr_fft_in[SNR_FFT_LEN]; + fcomplex_t snr_fft_out[SNR_FFT_LEN]; + float snr_power[SNR_FFT_LEN]; + int snr_cnt; + input_snr_cb_t snr_cb; + void *snr_cb_arg; + + acquire_t acq; + decode_t decode; + frame_t frame; + sync_t sync; +} input_t; + +void input_init(input_t *st, nrsc5_t *radio, output_t *output); +void input_set_mode(input_t *st); +void input_reset(input_t *st); +void input_free(input_t *st); +void input_set_sync_state(input_t *st, unsigned int new_state); +void input_push_cu8(input_t *st, uint8_t *buf, uint32_t len); +void input_push_cs16(input_t *st, int16_t *buf, uint32_t len); +void input_set_snr_callback(input_t *st, input_snr_cb_t cb, void *); +void input_set_skip(input_t *st, unsigned int skip); +void input_pdu_push(input_t *st, uint8_t *pdu, unsigned int len, unsigned int program, unsigned int stream_id); +void input_aas_push(input_t *st, uint8_t *psd, unsigned int len); diff --git a/src/hddsp/nrsc5.c b/src/hddsp/nrsc5.c index 6867d6e..b208b20 100644 --- a/src/hddsp/nrsc5.c +++ b/src/hddsp/nrsc5.c @@ -1,365 +1,365 @@ -#include -#include -#include - -#include "private.h" - -#ifdef __MINGW32__ -#define NRSC5_API __declspec(dllexport) -#else -#define NRSC5_API -#endif - -static void nrsc5_init(nrsc5_t *st) -{ - st->closed = 0; - st->mode = NRSC5_MODE_FM; - st->callback = NULL; - - output_init(&st->output, st); - input_init(&st->input, st, &st->output); -} - - -NRSC5_API void nrsc5_service_data_type_name(unsigned int type, const char **name) -{ - switch (type) - { - case NRSC5_SERVICE_DATA_TYPE_NON_SPECIFIC: *name = "Non-specific"; break; - case NRSC5_SERVICE_DATA_TYPE_NEWS: *name = "News"; break; - case NRSC5_SERVICE_DATA_TYPE_SPORTS: *name = "Sports"; break; - case NRSC5_SERVICE_DATA_TYPE_WEATHER: *name = "Weather"; break; - case NRSC5_SERVICE_DATA_TYPE_EMERGENCY: *name = "Emergency"; break; - case NRSC5_SERVICE_DATA_TYPE_TRAFFIC: *name = "Traffic"; break; - case NRSC5_SERVICE_DATA_TYPE_IMAGE_MAPS: *name = "Image Maps"; break; - case NRSC5_SERVICE_DATA_TYPE_TEXT: *name = "Text"; break; - case NRSC5_SERVICE_DATA_TYPE_ADVERTISING: *name = "Advertising"; break; - case NRSC5_SERVICE_DATA_TYPE_FINANCIAL: *name = "Financial"; break; - case NRSC5_SERVICE_DATA_TYPE_STOCK_TICKER: *name = "Stock Ticker"; break; - case NRSC5_SERVICE_DATA_TYPE_NAVIGATION: *name = "Navigation"; break; - case NRSC5_SERVICE_DATA_TYPE_ELECTRONIC_PROGRAM_GUIDE: *name = "Electronic Program Guide"; break; - case NRSC5_SERVICE_DATA_TYPE_AUDIO: *name = "Audio"; break; - case NRSC5_SERVICE_DATA_TYPE_PRIVATE_DATA_NETWORK: *name = "Private Data Network"; break; - case NRSC5_SERVICE_DATA_TYPE_SERVICE_MAINTENANCE: *name = "Service Maintenance"; break; - case NRSC5_SERVICE_DATA_TYPE_HD_RADIO_SYSTEM_SERVICES: *name = "HD Radio System Services"; break; - case NRSC5_SERVICE_DATA_TYPE_AUDIO_RELATED_DATA: *name = "Audio-Related Objects"; break; - default: *name = "Unknown"; break; - } -} - -NRSC5_API void nrsc5_program_type_name(unsigned int type, const char **name) -{ - switch (type) - { - case NRSC5_PROGRAM_TYPE_UNDEFINED: *name = "None"; break; - case NRSC5_PROGRAM_TYPE_NEWS: *name = "News"; break; - case NRSC5_PROGRAM_TYPE_INFORMATION: *name = "Information"; break; - case NRSC5_PROGRAM_TYPE_SPORTS: *name = "Sports"; break; - case NRSC5_PROGRAM_TYPE_TALK: *name = "Talk"; break; - case NRSC5_PROGRAM_TYPE_ROCK: *name = "Rock"; break; - case NRSC5_PROGRAM_TYPE_CLASSIC_ROCK: *name = "Classic Rock"; break; - case NRSC5_PROGRAM_TYPE_ADULT_HITS: *name = "Adult Hits"; break; - case NRSC5_PROGRAM_TYPE_SOFT_ROCK: *name = "Soft Rock"; break; - case NRSC5_PROGRAM_TYPE_TOP_40: *name = "Top 40"; break; - case NRSC5_PROGRAM_TYPE_COUNTRY: *name = "Country"; break; - case NRSC5_PROGRAM_TYPE_OLDIES: *name = "Oldies"; break; - case NRSC5_PROGRAM_TYPE_SOFT: *name = "Soft"; break; - case NRSC5_PROGRAM_TYPE_NOSTALGIA: *name = "Nostalgia"; break; - case NRSC5_PROGRAM_TYPE_JAZZ: *name = "Jazz"; break; - case NRSC5_PROGRAM_TYPE_CLASSICAL: *name = "Classical"; break; - case NRSC5_PROGRAM_TYPE_RHYTHM_AND_BLUES: *name = "Rhythm and Blues"; break; - case NRSC5_PROGRAM_TYPE_SOFT_RHYTHM_AND_BLUES: *name = "Soft Rhythm and Blues"; break; - case NRSC5_PROGRAM_TYPE_FOREIGN_LANGUAGE: *name = "Foreign Language"; break; - case NRSC5_PROGRAM_TYPE_RELIGIOUS_MUSIC: *name = "Religious Music"; break; - case NRSC5_PROGRAM_TYPE_RELIGIOUS_TALK: *name = "Religious Talk"; break; - case NRSC5_PROGRAM_TYPE_PERSONALITY: *name = "Personality"; break; - case NRSC5_PROGRAM_TYPE_PUBLIC: *name = "Public"; break; - case NRSC5_PROGRAM_TYPE_COLLEGE: *name = "College"; break; - case NRSC5_PROGRAM_TYPE_SPANISH_TALK: *name = "Spanish Talk"; break; - case NRSC5_PROGRAM_TYPE_SPANISH_MUSIC: *name = "Spanish Music"; break; - case NRSC5_PROGRAM_TYPE_HIP_HOP: *name = "Hip-Hop"; break; - case NRSC5_PROGRAM_TYPE_WEATHER: *name = "Weather"; break; - case NRSC5_PROGRAM_TYPE_EMERGENCY_TEST: *name = "Emergency Test"; break; - case NRSC5_PROGRAM_TYPE_EMERGENCY: *name = "Emergency"; break; - case NRSC5_PROGRAM_TYPE_TRAFFIC: *name = "Traffic"; break; - case NRSC5_PROGRAM_TYPE_SPECIAL_READING_SERVICES: *name = "Special Reading Services"; break; - default: *name = "Unknown"; break; - } -} - -static nrsc5_t *nrsc5_alloc() -{ - nrsc5_t *st = calloc(1, sizeof(*st)); - return st; -} - -NRSC5_API int nrsc5_open_pipe(nrsc5_t **result) -{ - nrsc5_t *st = nrsc5_alloc(); - nrsc5_init(st); - - *result = st; - return 0; -} - -NRSC5_API void nrsc5_close(nrsc5_t *st) -{ - if (!st) - return; - - st->closed = 1; - - input_free(&st->input); - output_free(&st->output); - free(st); -} - -NRSC5_API int nrsc5_set_mode(nrsc5_t *st, int mode) -{ - if (mode == NRSC5_MODE_FM || mode == NRSC5_MODE_AM) - { - st->mode = mode; - input_set_mode(&st->input); - return 0; - } - return 1; -} - -NRSC5_API void nrsc5_set_callback(nrsc5_t *st, nrsc5_callback_t callback, void *opaque) -{ - st->callback = callback; - st->callback_opaque = opaque; -} - -NRSC5_API int nrsc5_pipe_samples_cu8(nrsc5_t *st, uint8_t *samples, unsigned int length) -{ - input_push_cu8(&st->input, samples, length); - - return 0; -} - -NRSC5_API int nrsc5_pipe_samples_cs16(nrsc5_t *st, int16_t *samples, unsigned int length) -{ - input_push_cs16(&st->input, samples, length); - - return 0; -} - -void nrsc5_report(nrsc5_t *st, const nrsc5_event_t *evt) -{ - if (st->callback) - st->callback(evt, st->callback_opaque); -} - -void nrsc5_report_lost_device(nrsc5_t *st) -{ - nrsc5_event_t evt; - - evt.event = NRSC5_EVENT_LOST_DEVICE; - nrsc5_report(st, &evt); -} - -void nrsc5_report_iq(nrsc5_t *st, const void *data, size_t count) -{ - nrsc5_event_t evt; - - evt.event = NRSC5_EVENT_IQ; - evt.iq.data = data; - evt.iq.count = count; - nrsc5_report(st, &evt); -} - -void nrsc5_report_sync(nrsc5_t *st) -{ - nrsc5_event_t evt; - - evt.event = NRSC5_EVENT_SYNC; - nrsc5_report(st, &evt); -} - -void nrsc5_report_lost_sync(nrsc5_t *st) -{ - nrsc5_event_t evt; - - evt.event = NRSC5_EVENT_LOST_SYNC; - nrsc5_report(st, &evt); -} - -void nrsc5_report_hdc(nrsc5_t *st, unsigned int program, const uint8_t *data, size_t count) -{ - nrsc5_event_t evt; - - evt.event = NRSC5_EVENT_HDC; - evt.hdc.program = program; - evt.hdc.data = data; - evt.hdc.count = count; - nrsc5_report(st, &evt); -} - -void nrsc5_report_audio(nrsc5_t *st, unsigned int program, const int16_t *data, size_t count) -{ - nrsc5_event_t evt; - - evt.event = NRSC5_EVENT_AUDIO; - evt.audio.program = program; - evt.audio.data = data; - evt.audio.count = count; - nrsc5_report(st, &evt); -} - -void nrsc5_report_mer(nrsc5_t *st, float lower, float upper) -{ - nrsc5_event_t evt; - - evt.event = NRSC5_EVENT_MER; - evt.mer.lower = lower; - evt.mer.upper = upper; - nrsc5_report(st, &evt); -} - -void nrsc5_report_ber(nrsc5_t *st, float cber) -{ - nrsc5_event_t evt; - - evt.event = NRSC5_EVENT_BER; - evt.ber.cber = cber; - nrsc5_report(st, &evt); -} - -void nrsc5_report_lot(nrsc5_t *st, uint16_t port, unsigned int lot, unsigned int size, uint32_t mime, const char *name, const uint8_t *data) -{ - nrsc5_event_t evt; - - evt.event = NRSC5_EVENT_LOT; - evt.lot.port = port; - evt.lot.lot = lot; - evt.lot.size = size; - evt.lot.mime = mime; - evt.lot.name = name; - evt.lot.data = data; - nrsc5_report(st, &evt); -} - -static uint8_t convert_sig_component_type(uint8_t type) -{ - switch (type) - { - case SIG_COMPONENT_AUDIO: return NRSC5_SIG_COMPONENT_AUDIO; - case SIG_COMPONENT_DATA: return NRSC5_SIG_COMPONENT_DATA; - default: - assert(0 && "Invalid component type"); - return 0; - } -} - -static uint8_t convert_sig_service_type(uint8_t type) -{ - switch (type) - { - case SIG_SERVICE_AUDIO: return NRSC5_SIG_SERVICE_AUDIO; - case SIG_SERVICE_DATA: return NRSC5_SIG_SERVICE_DATA; - default: - assert(0 && "Invalid service type"); - return 0; - } -} - -void nrsc5_report_sig(nrsc5_t *st, sig_service_t *services, unsigned int count) -{ - nrsc5_sig_service_t *service = NULL; - nrsc5_event_t evt; - - evt.event = NRSC5_EVENT_SIG; - - // convert internal structures to public structures - for (unsigned int i = 0; i < count; i++) - { - nrsc5_sig_component_t *component = NULL; - nrsc5_sig_service_t *prev = service; - - service = calloc(1, sizeof(nrsc5_sig_service_t)); - service->type = convert_sig_service_type(services[i].type); - service->number = services[i].number; - service->name = services[i].name; - - if (prev == NULL) - evt.sig.services = service; - else - prev->next = service; - - for (unsigned int j = 0; j < MAX_SIG_COMPONENTS; j++) - { - nrsc5_sig_component_t *prevc = component; - sig_component_t *internal = &services[i].component[j]; - - if (internal->type == SIG_COMPONENT_NONE) - continue; - - component = calloc(1, sizeof(nrsc5_sig_component_t)); - component->type = convert_sig_component_type(internal->type); - component->id = internal->id; - - if (internal->type == SIG_COMPONENT_AUDIO) - { - component->audio.port = internal->audio.port; - component->audio.type = internal->audio.type; - component->audio.mime = internal->audio.mime; - } - else if (internal->type == SIG_COMPONENT_DATA) - { - component->data.port = internal->data.port; - component->data.service_data_type = internal->data.service_data_type; - component->data.type = internal->data.type; - component->data.mime = internal->data.mime; - } - - if (prevc == NULL) - service->components = component; - else - prevc->next = component; - } - } - - nrsc5_report(st, &evt); - - // free the data structures - for (service = evt.sig.services; service != NULL; ) - { - void *p; - nrsc5_sig_component_t *component; - - for (component = service->components; component != NULL; ) - { - p = component; - component = component->next; - free(p); - } - - p = service; - service = service->next; - free(p); - } -} - -void nrsc5_report_sis(nrsc5_t *st, const char *country_code, int fcc_facility_id, const char *name, - const char *slogan, const char *message, const char *alert, - float latitude, float longitude, int altitude, nrsc5_sis_asd_t *audio_services, - nrsc5_sis_dsd_t *data_services) -{ - nrsc5_event_t evt; - - evt.event = NRSC5_EVENT_SIS; - evt.sis.country_code = country_code; - evt.sis.fcc_facility_id = fcc_facility_id; - evt.sis.name = name; - evt.sis.slogan = slogan; - evt.sis.message = message; - evt.sis.alert = alert; - evt.sis.latitude = latitude; - evt.sis.longitude = longitude; - evt.sis.altitude = altitude; - evt.sis.audio_services = audio_services; - evt.sis.data_services = data_services; - - nrsc5_report(st, &evt); -} +#include +#include +#include + +#include "private.h" + +#ifdef __MINGW32__ +#define NRSC5_API __declspec(dllexport) +#else +#define NRSC5_API +#endif + +static void nrsc5_init(nrsc5_t *st) +{ + st->closed = 0; + st->mode = NRSC5_MODE_FM; + st->callback = NULL; + + output_init(&st->output, st); + input_init(&st->input, st, &st->output); +} + + +NRSC5_API void nrsc5_service_data_type_name(unsigned int type, const char **name) +{ + switch (type) + { + case NRSC5_SERVICE_DATA_TYPE_NON_SPECIFIC: *name = "Non-specific"; break; + case NRSC5_SERVICE_DATA_TYPE_NEWS: *name = "News"; break; + case NRSC5_SERVICE_DATA_TYPE_SPORTS: *name = "Sports"; break; + case NRSC5_SERVICE_DATA_TYPE_WEATHER: *name = "Weather"; break; + case NRSC5_SERVICE_DATA_TYPE_EMERGENCY: *name = "Emergency"; break; + case NRSC5_SERVICE_DATA_TYPE_TRAFFIC: *name = "Traffic"; break; + case NRSC5_SERVICE_DATA_TYPE_IMAGE_MAPS: *name = "Image Maps"; break; + case NRSC5_SERVICE_DATA_TYPE_TEXT: *name = "Text"; break; + case NRSC5_SERVICE_DATA_TYPE_ADVERTISING: *name = "Advertising"; break; + case NRSC5_SERVICE_DATA_TYPE_FINANCIAL: *name = "Financial"; break; + case NRSC5_SERVICE_DATA_TYPE_STOCK_TICKER: *name = "Stock Ticker"; break; + case NRSC5_SERVICE_DATA_TYPE_NAVIGATION: *name = "Navigation"; break; + case NRSC5_SERVICE_DATA_TYPE_ELECTRONIC_PROGRAM_GUIDE: *name = "Electronic Program Guide"; break; + case NRSC5_SERVICE_DATA_TYPE_AUDIO: *name = "Audio"; break; + case NRSC5_SERVICE_DATA_TYPE_PRIVATE_DATA_NETWORK: *name = "Private Data Network"; break; + case NRSC5_SERVICE_DATA_TYPE_SERVICE_MAINTENANCE: *name = "Service Maintenance"; break; + case NRSC5_SERVICE_DATA_TYPE_HD_RADIO_SYSTEM_SERVICES: *name = "HD Radio System Services"; break; + case NRSC5_SERVICE_DATA_TYPE_AUDIO_RELATED_DATA: *name = "Audio-Related Objects"; break; + default: *name = "Unknown"; break; + } +} + +NRSC5_API void nrsc5_program_type_name(unsigned int type, const char **name) +{ + switch (type) + { + case NRSC5_PROGRAM_TYPE_UNDEFINED: *name = "None"; break; + case NRSC5_PROGRAM_TYPE_NEWS: *name = "News"; break; + case NRSC5_PROGRAM_TYPE_INFORMATION: *name = "Information"; break; + case NRSC5_PROGRAM_TYPE_SPORTS: *name = "Sports"; break; + case NRSC5_PROGRAM_TYPE_TALK: *name = "Talk"; break; + case NRSC5_PROGRAM_TYPE_ROCK: *name = "Rock"; break; + case NRSC5_PROGRAM_TYPE_CLASSIC_ROCK: *name = "Classic Rock"; break; + case NRSC5_PROGRAM_TYPE_ADULT_HITS: *name = "Adult Hits"; break; + case NRSC5_PROGRAM_TYPE_SOFT_ROCK: *name = "Soft Rock"; break; + case NRSC5_PROGRAM_TYPE_TOP_40: *name = "Top 40"; break; + case NRSC5_PROGRAM_TYPE_COUNTRY: *name = "Country"; break; + case NRSC5_PROGRAM_TYPE_OLDIES: *name = "Oldies"; break; + case NRSC5_PROGRAM_TYPE_SOFT: *name = "Soft"; break; + case NRSC5_PROGRAM_TYPE_NOSTALGIA: *name = "Nostalgia"; break; + case NRSC5_PROGRAM_TYPE_JAZZ: *name = "Jazz"; break; + case NRSC5_PROGRAM_TYPE_CLASSICAL: *name = "Classical"; break; + case NRSC5_PROGRAM_TYPE_RHYTHM_AND_BLUES: *name = "Rhythm and Blues"; break; + case NRSC5_PROGRAM_TYPE_SOFT_RHYTHM_AND_BLUES: *name = "Soft Rhythm and Blues"; break; + case NRSC5_PROGRAM_TYPE_FOREIGN_LANGUAGE: *name = "Foreign Language"; break; + case NRSC5_PROGRAM_TYPE_RELIGIOUS_MUSIC: *name = "Religious Music"; break; + case NRSC5_PROGRAM_TYPE_RELIGIOUS_TALK: *name = "Religious Talk"; break; + case NRSC5_PROGRAM_TYPE_PERSONALITY: *name = "Personality"; break; + case NRSC5_PROGRAM_TYPE_PUBLIC: *name = "Public"; break; + case NRSC5_PROGRAM_TYPE_COLLEGE: *name = "College"; break; + case NRSC5_PROGRAM_TYPE_SPANISH_TALK: *name = "Spanish Talk"; break; + case NRSC5_PROGRAM_TYPE_SPANISH_MUSIC: *name = "Spanish Music"; break; + case NRSC5_PROGRAM_TYPE_HIP_HOP: *name = "Hip-Hop"; break; + case NRSC5_PROGRAM_TYPE_WEATHER: *name = "Weather"; break; + case NRSC5_PROGRAM_TYPE_EMERGENCY_TEST: *name = "Emergency Test"; break; + case NRSC5_PROGRAM_TYPE_EMERGENCY: *name = "Emergency"; break; + case NRSC5_PROGRAM_TYPE_TRAFFIC: *name = "Traffic"; break; + case NRSC5_PROGRAM_TYPE_SPECIAL_READING_SERVICES: *name = "Special Reading Services"; break; + default: *name = "Unknown"; break; + } +} + +static nrsc5_t *nrsc5_alloc() +{ + nrsc5_t *st = calloc(1, sizeof(*st)); + return st; +} + +NRSC5_API int nrsc5_open_pipe(nrsc5_t **result) +{ + nrsc5_t *st = nrsc5_alloc(); + nrsc5_init(st); + + *result = st; + return 0; +} + +NRSC5_API void nrsc5_close(nrsc5_t *st) +{ + if (!st) + return; + + st->closed = 1; + + input_free(&st->input); + output_free(&st->output); + free(st); +} + +NRSC5_API int nrsc5_set_mode(nrsc5_t *st, int mode) +{ + if (mode == NRSC5_MODE_FM || mode == NRSC5_MODE_AM) + { + st->mode = mode; + input_set_mode(&st->input); + return 0; + } + return 1; +} + +NRSC5_API void nrsc5_set_callback(nrsc5_t *st, nrsc5_callback_t callback, void *opaque) +{ + st->callback = callback; + st->callback_opaque = opaque; +} + +NRSC5_API int nrsc5_pipe_samples_cu8(nrsc5_t *st, uint8_t *samples, unsigned int length) +{ + input_push_cu8(&st->input, samples, length); + + return 0; +} + +NRSC5_API int nrsc5_pipe_samples_cs16(nrsc5_t *st, int16_t *samples, unsigned int length) +{ + input_push_cs16(&st->input, samples, length); + + return 0; +} + +void nrsc5_report(nrsc5_t *st, const nrsc5_event_t *evt) +{ + if (st->callback) + st->callback(evt, st->callback_opaque); +} + +void nrsc5_report_lost_device(nrsc5_t *st) +{ + nrsc5_event_t evt; + + evt.event = NRSC5_EVENT_LOST_DEVICE; + nrsc5_report(st, &evt); +} + +void nrsc5_report_iq(nrsc5_t *st, const void *data, size_t count) +{ + nrsc5_event_t evt; + + evt.event = NRSC5_EVENT_IQ; + evt.iq.data = data; + evt.iq.count = count; + nrsc5_report(st, &evt); +} + +void nrsc5_report_sync(nrsc5_t *st) +{ + nrsc5_event_t evt; + + evt.event = NRSC5_EVENT_SYNC; + nrsc5_report(st, &evt); +} + +void nrsc5_report_lost_sync(nrsc5_t *st) +{ + nrsc5_event_t evt; + + evt.event = NRSC5_EVENT_LOST_SYNC; + nrsc5_report(st, &evt); +} + +void nrsc5_report_hdc(nrsc5_t *st, unsigned int program, const uint8_t *data, size_t count) +{ + nrsc5_event_t evt; + + evt.event = NRSC5_EVENT_HDC; + evt.hdc.program = program; + evt.hdc.data = data; + evt.hdc.count = count; + nrsc5_report(st, &evt); +} + +void nrsc5_report_audio(nrsc5_t *st, unsigned int program, const int16_t *data, size_t count) +{ + nrsc5_event_t evt; + + evt.event = NRSC5_EVENT_AUDIO; + evt.audio.program = program; + evt.audio.data = data; + evt.audio.count = count; + nrsc5_report(st, &evt); +} + +void nrsc5_report_mer(nrsc5_t *st, float lower, float upper) +{ + nrsc5_event_t evt; + + evt.event = NRSC5_EVENT_MER; + evt.mer.lower = lower; + evt.mer.upper = upper; + nrsc5_report(st, &evt); +} + +void nrsc5_report_ber(nrsc5_t *st, float cber) +{ + nrsc5_event_t evt; + + evt.event = NRSC5_EVENT_BER; + evt.ber.cber = cber; + nrsc5_report(st, &evt); +} + +void nrsc5_report_lot(nrsc5_t *st, uint16_t port, unsigned int lot, unsigned int size, uint32_t mime, const char *name, const uint8_t *data) +{ + nrsc5_event_t evt; + + evt.event = NRSC5_EVENT_LOT; + evt.lot.port = port; + evt.lot.lot = lot; + evt.lot.size = size; + evt.lot.mime = mime; + evt.lot.name = name; + evt.lot.data = data; + nrsc5_report(st, &evt); +} + +static uint8_t convert_sig_component_type(uint8_t type) +{ + switch (type) + { + case SIG_COMPONENT_AUDIO: return NRSC5_SIG_COMPONENT_AUDIO; + case SIG_COMPONENT_DATA: return NRSC5_SIG_COMPONENT_DATA; + default: + assert(0 && "Invalid component type"); + return 0; + } +} + +static uint8_t convert_sig_service_type(uint8_t type) +{ + switch (type) + { + case SIG_SERVICE_AUDIO: return NRSC5_SIG_SERVICE_AUDIO; + case SIG_SERVICE_DATA: return NRSC5_SIG_SERVICE_DATA; + default: + assert(0 && "Invalid service type"); + return 0; + } +} + +void nrsc5_report_sig(nrsc5_t *st, sig_service_t *services, unsigned int count) +{ + nrsc5_sig_service_t *service = NULL; + nrsc5_event_t evt; + + evt.event = NRSC5_EVENT_SIG; + + // convert internal structures to public structures + for (unsigned int i = 0; i < count; i++) + { + nrsc5_sig_component_t *component = NULL; + nrsc5_sig_service_t *prev = service; + + service = calloc(1, sizeof(nrsc5_sig_service_t)); + service->type = convert_sig_service_type(services[i].type); + service->number = services[i].number; + service->name = services[i].name; + + if (prev == NULL) + evt.sig.services = service; + else + prev->next = service; + + for (unsigned int j = 0; j < MAX_SIG_COMPONENTS; j++) + { + nrsc5_sig_component_t *prevc = component; + sig_component_t *internal = &services[i].component[j]; + + if (internal->type == SIG_COMPONENT_NONE) + continue; + + component = calloc(1, sizeof(nrsc5_sig_component_t)); + component->type = convert_sig_component_type(internal->type); + component->id = internal->id; + + if (internal->type == SIG_COMPONENT_AUDIO) + { + component->audio.port = internal->audio.port; + component->audio.type = internal->audio.type; + component->audio.mime = internal->audio.mime; + } + else if (internal->type == SIG_COMPONENT_DATA) + { + component->data.port = internal->data.port; + component->data.service_data_type = internal->data.service_data_type; + component->data.type = internal->data.type; + component->data.mime = internal->data.mime; + } + + if (prevc == NULL) + service->components = component; + else + prevc->next = component; + } + } + + nrsc5_report(st, &evt); + + // free the data structures + for (service = evt.sig.services; service != NULL; ) + { + void *p; + nrsc5_sig_component_t *component; + + for (component = service->components; component != NULL; ) + { + p = component; + component = component->next; + free(p); + } + + p = service; + service = service->next; + free(p); + } +} + +void nrsc5_report_sis(nrsc5_t *st, const char *country_code, int fcc_facility_id, const char *name, + const char *slogan, const char *message, const char *alert, + float latitude, float longitude, int altitude, nrsc5_sis_asd_t *audio_services, + nrsc5_sis_dsd_t *data_services) +{ + nrsc5_event_t evt; + + evt.event = NRSC5_EVENT_SIS; + evt.sis.country_code = country_code; + evt.sis.fcc_facility_id = fcc_facility_id; + evt.sis.name = name; + evt.sis.slogan = slogan; + evt.sis.message = message; + evt.sis.alert = alert; + evt.sis.latitude = latitude; + evt.sis.longitude = longitude; + evt.sis.altitude = altitude; + evt.sis.audio_services = audio_services; + evt.sis.data_services = data_services; + + nrsc5_report(st, &evt); +} diff --git a/src/hddsp/nrsc5.h b/src/hddsp/nrsc5.h index 2335c58..7f8134a 100644 --- a/src/hddsp/nrsc5.h +++ b/src/hddsp/nrsc5.h @@ -1,455 +1,455 @@ -#ifndef NRSC5_H_ -#define NRSC5_H_ - -/** \file nrsc5.h - * External API for the nrsc5 library. - */ - -/*! \mainpage Quick API Reference - * - * The library API is documented in nrsc5.h -- see the *Functions* section. - * - * Useful information about the protocol is provided in standards - * documents available from https://www.nrscstandards.org/ - * - * Note: this documentation was *not* written by the authors of **nrsc5**; - * its accuracy cannot be guaranteed. - */ - -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/* - * Definitions. - */ -#define NRSC5_SCAN_BEGIN 87.9e6 -#define NRSC5_SCAN_END 107.9e6 -#define NRSC5_SCAN_SKIP 0.2e6 - -#define NRSC5_MIME_PRIMARY_IMAGE 0xBE4B7536 -#define NRSC5_MIME_STATION_LOGO 0xD9C72536 -#define NRSC5_MIME_NAVTEQ 0x2D42AC3E -#define NRSC5_MIME_HERE_TPEG 0x82F03DFC -#define NRSC5_MIME_HERE_IMAGE 0xB7F03DFC -#define NRSC5_MIME_HD_TMC 0xEECB55B6 -#define NRSC5_MIME_HDC 0x4DC66C5A /**< High Defn Coding audio */ -#define NRSC5_MIME_TEXT 0xBB492AAC -#define NRSC5_MIME_JPEG 0x1E653E9C -#define NRSC5_MIME_PNG 0x4F328CA0 -#define NRSC5_MIME_TTN_TPEG_1 0xB39EBEB2 -#define NRSC5_MIME_TTN_TPEG_2 0x4EB03469 -#define NRSC5_MIME_TTN_TPEG_3 0x52103469 -#define NRSC5_MIME_TTN_STM_TRAFFIC 0xFF8422D7 -#define NRSC5_MIME_TTN_STM_WEATHER 0xEF042E96 - -enum -{ - NRSC5_MODE_FM, - NRSC5_MODE_AM -}; - -/* - * Data types. - */ -enum -{ - NRSC5_SIG_COMPONENT_AUDIO, - NRSC5_SIG_COMPONENT_DATA -}; - -/** Represent a channel component. - * - * An element of a linked list that accompanies a nrsc5_sig_service_t - * describing a component of a SIG record. This provides further - * information about the audio or data in the channel. - */ -struct nrsc5_sig_component_t -{ - struct nrsc5_sig_component_t *next; /**< Pointer to next element or NULL */ - uint8_t type; /**< NRSC5_SIG_SERVICE_AUDIO or NRSC5_SIG_SERVICE_DATA */ - uint8_t id; /**< Component identifier, 0, 1, 2,... */ - union - { - /*! Data service information - */ - struct { - uint16_t port; /**< distinguishes packets for this service */ - /** e.g. NRSC5_SERVICE_DATA_TYPE_AUDIO_RELATED_DATA */ - uint16_t service_data_type; - uint8_t type; /**< 0 for stream, 1 for packet, 3 for LOT */ - uint32_t mime; /**< content, e.g. NRSC5_MIME_STATION_LOGO */ - } data; - /*! Audio service information - */ - struct { - uint8_t port; /**< distinguishes packets for this service */ - uint8_t type; /**< 0 for stream, 1 for packet, 3 for LOT */ - uint32_t mime; /**< content, e.g. NRSC5_MIME_HDC */ - } audio; - }; -}; -/** - * Defines a typename for struct nrsc5_sig_component_t - */ -typedef struct nrsc5_sig_component_t nrsc5_sig_component_t; - -enum -{ - NRSC5_SIG_SERVICE_AUDIO, - NRSC5_SIG_SERVICE_DATA -}; - -/** Represent Station Information Guide (SIG) records - * - * Element of a linked list that will accompany an NRSC5_EVENT_SIG type event. - * Each cell describes a channel (audio or data), with a name, e.g. "MPS" - * is used to indicate main program service, "SPS1" to indicate - * supplemental program service 1. Each service may include a linked - * list of type nrsc5_sig_component_t describing its components, which - * may include both audio and other data. - */ -struct nrsc5_sig_service_t -{ - struct nrsc5_sig_service_t *next; /**< Pointer to next element or NULL */ - uint8_t type; /**< NRSC5_SIG_SERVICE_AUDIO or NRSC5_SIG_SERVICE_DATA */ - uint16_t number; /**< Channel number: 1,2,3,4 */ - const char *name; /**< Channel name, e.g. "MPS" or "SPS1" */ - nrsc5_sig_component_t *components; /**< Head of linked list of components */ -}; -/** - * Defines a typename for struct nrsc5_sig_service_t - */ -typedef struct nrsc5_sig_service_t nrsc5_sig_service_t; - -enum -{ - NRSC5_EVENT_LOST_DEVICE, - NRSC5_EVENT_IQ, - NRSC5_EVENT_SYNC, - NRSC5_EVENT_LOST_SYNC, - NRSC5_EVENT_MER, - NRSC5_EVENT_BER, - NRSC5_EVENT_HDC, - NRSC5_EVENT_AUDIO, - NRSC5_EVENT_ID3, - NRSC5_EVENT_SIG, - NRSC5_EVENT_LOT, - NRSC5_EVENT_SIS -}; - -enum -{ - NRSC5_ACCESS_PUBLIC, - NRSC5_ACCESS_RESTRICTED -}; - -enum -{ - NRSC5_PROGRAM_TYPE_UNDEFINED = 0, - NRSC5_PROGRAM_TYPE_NEWS = 1, - NRSC5_PROGRAM_TYPE_INFORMATION = 2, - NRSC5_PROGRAM_TYPE_SPORTS = 3, - NRSC5_PROGRAM_TYPE_TALK = 4, - NRSC5_PROGRAM_TYPE_ROCK = 5, - NRSC5_PROGRAM_TYPE_CLASSIC_ROCK = 6, - NRSC5_PROGRAM_TYPE_ADULT_HITS = 7, - NRSC5_PROGRAM_TYPE_SOFT_ROCK = 8, - NRSC5_PROGRAM_TYPE_TOP_40 = 9, - NRSC5_PROGRAM_TYPE_COUNTRY = 10, - NRSC5_PROGRAM_TYPE_OLDIES = 11, - NRSC5_PROGRAM_TYPE_SOFT = 12, - NRSC5_PROGRAM_TYPE_NOSTALGIA = 13, - NRSC5_PROGRAM_TYPE_JAZZ = 14, - NRSC5_PROGRAM_TYPE_CLASSICAL = 15, - NRSC5_PROGRAM_TYPE_RHYTHM_AND_BLUES = 16, - NRSC5_PROGRAM_TYPE_SOFT_RHYTHM_AND_BLUES = 17, - NRSC5_PROGRAM_TYPE_FOREIGN_LANGUAGE = 18, - NRSC5_PROGRAM_TYPE_RELIGIOUS_MUSIC = 19, - NRSC5_PROGRAM_TYPE_RELIGIOUS_TALK = 20, - NRSC5_PROGRAM_TYPE_PERSONALITY = 21, - NRSC5_PROGRAM_TYPE_PUBLIC = 22, - NRSC5_PROGRAM_TYPE_COLLEGE = 23, - NRSC5_PROGRAM_TYPE_SPANISH_TALK = 24, - NRSC5_PROGRAM_TYPE_SPANISH_MUSIC = 25, - NRSC5_PROGRAM_TYPE_HIP_HOP = 26, - NRSC5_PROGRAM_TYPE_WEATHER = 29, - NRSC5_PROGRAM_TYPE_EMERGENCY_TEST = 30, - NRSC5_PROGRAM_TYPE_EMERGENCY = 31, - NRSC5_PROGRAM_TYPE_TRAFFIC = 65, - NRSC5_PROGRAM_TYPE_SPECIAL_READING_SERVICES = 76 -}; - -/** - * Station Information Service *Audio* service descriptor. This is a - * linked list element that may point to further audio service - * descriptor elements. Refer to NRSC-5 document SY_IDD_1020s. - */ -struct nrsc5_sis_asd_t -{ - struct nrsc5_sis_asd_t *next; /**< Pointer to next element or NULL */ - unsigned int program; /**< program number 0, 1, ..., 7 */ - unsigned int access; /**< NRSC5_ACCESS_PUBLIC or NRSC5_ACCESS_RESTRICTED */ - unsigned int type; /**< audio service, e.g. NRSC5_PROGRAM_TYPE_JAZZ */ - unsigned int sound_exp; /**< 0 is none, 2 is Dolby Pro Logic II Surround */ -}; -/** - * Defines a typename for struct nrsc5_sis_asd_t - */ -typedef struct nrsc5_sis_asd_t nrsc5_sis_asd_t; - -enum -{ - NRSC5_SERVICE_DATA_TYPE_NON_SPECIFIC = 0, - NRSC5_SERVICE_DATA_TYPE_NEWS = 1, - NRSC5_SERVICE_DATA_TYPE_SPORTS = 3, - NRSC5_SERVICE_DATA_TYPE_WEATHER = 29, - NRSC5_SERVICE_DATA_TYPE_EMERGENCY = 31, - NRSC5_SERVICE_DATA_TYPE_TRAFFIC = 65, - NRSC5_SERVICE_DATA_TYPE_IMAGE_MAPS = 66, - NRSC5_SERVICE_DATA_TYPE_TEXT = 80, - NRSC5_SERVICE_DATA_TYPE_ADVERTISING = 256, - NRSC5_SERVICE_DATA_TYPE_FINANCIAL = 257, - NRSC5_SERVICE_DATA_TYPE_STOCK_TICKER = 258, - NRSC5_SERVICE_DATA_TYPE_NAVIGATION = 259, - NRSC5_SERVICE_DATA_TYPE_ELECTRONIC_PROGRAM_GUIDE = 260, - NRSC5_SERVICE_DATA_TYPE_AUDIO = 261, - NRSC5_SERVICE_DATA_TYPE_PRIVATE_DATA_NETWORK = 262, - NRSC5_SERVICE_DATA_TYPE_SERVICE_MAINTENANCE = 263, - NRSC5_SERVICE_DATA_TYPE_HD_RADIO_SYSTEM_SERVICES = 264, - NRSC5_SERVICE_DATA_TYPE_AUDIO_RELATED_DATA = 265 -}; - -/** - * Station Information Service *Data* service descriptor. This is a - * linked list element that may point to further data service descriptors - * via `next` member if not `NULL`. See - * nrsc5_service_data_type_name() for named types of data. - * Refer to NRSC-5 document SY_IDD_1020s. - */ -struct nrsc5_sis_dsd_t -{ - struct nrsc5_sis_dsd_t *next; /**< Pointer to next element or NULL */ - unsigned int access; /**< NRSC5_ACCESS_PUBLIC or NRSC5_ACCESS_RESTRICTED */ - unsigned int type; /**< data service type, e.g. NRSC5_SERVICE_DATA_TYPE_TEXT */ - uint32_t mime_type; /**< MIME type, e.g. `NRSC5_MIME_TEXT` */ -}; -/** - * Defines a typename for struct nrsc5_sis_dsd_t - */ -typedef struct nrsc5_sis_dsd_t nrsc5_sis_dsd_t; - -/** Incoming event from receiver. - * - * This event structure is passed to your application supplied - * callback function--see nrsc5_set_callback(). It is a union of - * various structs, keyed by member `event`. - */ -struct nrsc5_event_t -{ -/*! Type of event. - * The member `event` determines which sort of event occurred: - * - `NRSC5_EVENT_LOST_DEVICE` : signal is over - * - `NRSC5_EVENT_BER` : Bit Error Ratio data, see the `ber` union member - * - `NRSC5_EVENT_MER` : modulation error ratio, see the `mer` union member, - * and NRSC5 document SY_TN_2646s - * - `NRSC5_EVENT_IQ` : IQ data, see the `iq` union member - * - `NRSC5_EVENT_HD`C : HDC audio packet, see the `hdc` union member - * - `NRSC5_EVENT_AUDIO` : audio buffer, see the `audio` union member - * - `NRSC5_EVENT_SYNC` : indicates synchronization achieved - * - `NRSC5_EVENT_LOST_SYNC` : indicates synchronization lost - * - `NRSC5_EVENT_ID3` : ID3 information packet arrived, see `id3` member - * and information in HD-Radio document SY_IDD_1028s. - * - `NRSC5_EVENT_SIG` : service information arrived, see `sig` member - * - `NRSC5_EVENT_LOT` : LOT file data available, see `lot` member - * - `NRSC5_EVENT_SIS` : station information, see `sis` member - */ - unsigned int event; - union - { - struct { - const void *data; - size_t count; - } iq; - struct { - float cber; - } ber; - struct { - float lower; - float upper; - } mer; - struct { - unsigned int program; - const uint8_t *data; - size_t count; - } hdc; - struct { - unsigned int program; - const int16_t *data; - size_t count; - } audio; - struct { - unsigned int program; - const char *title; - const char *artist; - const char *album; - const char *genre; - struct { - unsigned int size; - const uint8_t* data; - } raw; - struct { - const char *owner; - const char *id; - } ufid; - struct { - uint32_t mime; - int param; - int lot; - } xhdr; - } id3; - struct { - uint16_t port; - unsigned int lot; - unsigned int size; - uint32_t mime; - const char *name; - const uint8_t *data; - } lot; - struct { - nrsc5_sig_service_t *services; - } sig; - struct { - const char *country_code; - int fcc_facility_id; - const char *name; - const char *slogan; - const char *message; - const char *alert; - float latitude; - float longitude; - int altitude; - nrsc5_sis_asd_t *audio_services; - nrsc5_sis_dsd_t *data_services; - } sis; - }; -}; -/** - * Defines a typename for struct nrsc5_event_t - */ -typedef struct nrsc5_event_t nrsc5_event_t; - -/** - * Prototype for a user event handling callback function. - * The function will be invoked with a pointer to the nrsc5_event_t - * and a pointer to a second contextual argument, established by - * nrsc5_set_callback(). - */ -typedef void (*nrsc5_callback_t)(const nrsc5_event_t *evt, void *opaque); - -/** - * An opaque data type used by API functions to represent session information. - * Applications should acquire a pointer to one via the `open_` functions: - * nrsc5_open(), nrsc5_open_file(), nrsc5_open_pipe(), or nrsc5_open_rtltcp(). - */ -typedef struct nrsc5_t nrsc5_t; - - -/* ============================================================================ - * Public functions. All functions return void or an error code (0 == success). - */ - -/** - * Retrieves a string corresponding to a service data type. - * @param[in] type a service data type integer. - * @param[out] name character pointer to a string naming the service type - * @return Nothing is returned - * - * This name will be quite short, e.g. "News" or "Weather". If the type is - * not recognized, it will the string "Unknown". - */ -void nrsc5_service_data_type_name(unsigned int type, const char **name); - -/** - * Retrieves a string corresponding to a program type. - * @param[in] type a protram data type integer. - * @param[out] name character pointer to a string naming the service type - * @return Nothing is returned - * - * This name will be quite short, e.g. "News" or "Rock". If the type is - * not recognized, it will the string "Unknown". - */ -void nrsc5_program_type_name(unsigned int type, const char **name); - -/** - * Initializes a session for use with a pipe. - * @param[out] st handle for an `nrsc5_t` - * @return 0 on success, nonzero on error - * - */ -int nrsc5_open_pipe(nrsc5_t **st ); - -/** - * Closes an nrsc5 session. - * @param[in] st pointer to an `nrsc5_t` - * @return Nothing is returned. - * - * Any worker thread is signalled to exit, files and sockets are closed, - * and I/O buffers are freed. - */ -void nrsc5_close(nrsc5_t *); - -/** - * Set the session mode to AM or FM. - * @param[in] st pointer to an `nrsc5_t` session object - * @param[in] mode either `NRSC5_MODE_FM` or `NRSC5_MODE_AM` - * @return 0 on success or nonzero on error. - * - */ -int nrsc5_set_mode(nrsc5_t *, int mode); - -/** - * Establish a callback function. - * - * @param[in] st pointer to an `nrsc5_t` session object - * @param[in] callback pointer to an event handling function of two arguments - * @param[in] opaque pointer to the function's intended 2nd argument - * @return Nothing is returned. - * - */ -void nrsc5_set_callback(nrsc5_t *st, nrsc5_callback_t callback, void *opaque); - - -/** - * Push an IQ array of 8-bit unsigned samples into the demodulator. - * - * @param[in] st pointer to an `nrsc5_t` session object - * @param[in] samples pointer to an array 8-bit unsigned samples - * @param[in] length the number of samples in the array - * @return 0 on success, nonzero on error - * - */ -int nrsc5_pipe_samples_cu8(nrsc5_t *st, uint8_t *samples, unsigned int length); - - -/** - * Push an IQ input array of 16-bit signed samples into the demodulator. - * - * @param[in] st pointer to an `nrsc5_t` session object - * @param[in] samples pointer to an array 16-bit signed samples - * @param[in] length the number of samples in the array - * @return 0 on success, nonzero on error - * - */ -int nrsc5_pipe_samples_cs16(nrsc5_t *st, int16_t *samples, unsigned int length); - -#ifdef __cplusplus -} -#endif - -#endif /* NRSC5_H_ */ - +#ifndef NRSC5_H_ +#define NRSC5_H_ + +/** \file nrsc5.h + * External API for the nrsc5 library. + */ + +/*! \mainpage Quick API Reference + * + * The library API is documented in nrsc5.h -- see the *Functions* section. + * + * Useful information about the protocol is provided in standards + * documents available from https://www.nrscstandards.org/ + * + * Note: this documentation was *not* written by the authors of **nrsc5**; + * its accuracy cannot be guaranteed. + */ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Definitions. + */ +#define NRSC5_SCAN_BEGIN 87.9e6 +#define NRSC5_SCAN_END 107.9e6 +#define NRSC5_SCAN_SKIP 0.2e6 + +#define NRSC5_MIME_PRIMARY_IMAGE 0xBE4B7536 +#define NRSC5_MIME_STATION_LOGO 0xD9C72536 +#define NRSC5_MIME_NAVTEQ 0x2D42AC3E +#define NRSC5_MIME_HERE_TPEG 0x82F03DFC +#define NRSC5_MIME_HERE_IMAGE 0xB7F03DFC +#define NRSC5_MIME_HD_TMC 0xEECB55B6 +#define NRSC5_MIME_HDC 0x4DC66C5A /**< High Defn Coding audio */ +#define NRSC5_MIME_TEXT 0xBB492AAC +#define NRSC5_MIME_JPEG 0x1E653E9C +#define NRSC5_MIME_PNG 0x4F328CA0 +#define NRSC5_MIME_TTN_TPEG_1 0xB39EBEB2 +#define NRSC5_MIME_TTN_TPEG_2 0x4EB03469 +#define NRSC5_MIME_TTN_TPEG_3 0x52103469 +#define NRSC5_MIME_TTN_STM_TRAFFIC 0xFF8422D7 +#define NRSC5_MIME_TTN_STM_WEATHER 0xEF042E96 + +enum +{ + NRSC5_MODE_FM, + NRSC5_MODE_AM +}; + +/* + * Data types. + */ +enum +{ + NRSC5_SIG_COMPONENT_AUDIO, + NRSC5_SIG_COMPONENT_DATA +}; + +/** Represent a channel component. + * + * An element of a linked list that accompanies a nrsc5_sig_service_t + * describing a component of a SIG record. This provides further + * information about the audio or data in the channel. + */ +struct nrsc5_sig_component_t +{ + struct nrsc5_sig_component_t *next; /**< Pointer to next element or NULL */ + uint8_t type; /**< NRSC5_SIG_SERVICE_AUDIO or NRSC5_SIG_SERVICE_DATA */ + uint8_t id; /**< Component identifier, 0, 1, 2,... */ + union + { + /*! Data service information + */ + struct { + uint16_t port; /**< distinguishes packets for this service */ + /** e.g. NRSC5_SERVICE_DATA_TYPE_AUDIO_RELATED_DATA */ + uint16_t service_data_type; + uint8_t type; /**< 0 for stream, 1 for packet, 3 for LOT */ + uint32_t mime; /**< content, e.g. NRSC5_MIME_STATION_LOGO */ + } data; + /*! Audio service information + */ + struct { + uint8_t port; /**< distinguishes packets for this service */ + uint8_t type; /**< 0 for stream, 1 for packet, 3 for LOT */ + uint32_t mime; /**< content, e.g. NRSC5_MIME_HDC */ + } audio; + }; +}; +/** + * Defines a typename for struct nrsc5_sig_component_t + */ +typedef struct nrsc5_sig_component_t nrsc5_sig_component_t; + +enum +{ + NRSC5_SIG_SERVICE_AUDIO, + NRSC5_SIG_SERVICE_DATA +}; + +/** Represent Station Information Guide (SIG) records + * + * Element of a linked list that will accompany an NRSC5_EVENT_SIG type event. + * Each cell describes a channel (audio or data), with a name, e.g. "MPS" + * is used to indicate main program service, "SPS1" to indicate + * supplemental program service 1. Each service may include a linked + * list of type nrsc5_sig_component_t describing its components, which + * may include both audio and other data. + */ +struct nrsc5_sig_service_t +{ + struct nrsc5_sig_service_t *next; /**< Pointer to next element or NULL */ + uint8_t type; /**< NRSC5_SIG_SERVICE_AUDIO or NRSC5_SIG_SERVICE_DATA */ + uint16_t number; /**< Channel number: 1,2,3,4 */ + const char *name; /**< Channel name, e.g. "MPS" or "SPS1" */ + nrsc5_sig_component_t *components; /**< Head of linked list of components */ +}; +/** + * Defines a typename for struct nrsc5_sig_service_t + */ +typedef struct nrsc5_sig_service_t nrsc5_sig_service_t; + +enum +{ + NRSC5_EVENT_LOST_DEVICE, + NRSC5_EVENT_IQ, + NRSC5_EVENT_SYNC, + NRSC5_EVENT_LOST_SYNC, + NRSC5_EVENT_MER, + NRSC5_EVENT_BER, + NRSC5_EVENT_HDC, + NRSC5_EVENT_AUDIO, + NRSC5_EVENT_ID3, + NRSC5_EVENT_SIG, + NRSC5_EVENT_LOT, + NRSC5_EVENT_SIS +}; + +enum +{ + NRSC5_ACCESS_PUBLIC, + NRSC5_ACCESS_RESTRICTED +}; + +enum +{ + NRSC5_PROGRAM_TYPE_UNDEFINED = 0, + NRSC5_PROGRAM_TYPE_NEWS = 1, + NRSC5_PROGRAM_TYPE_INFORMATION = 2, + NRSC5_PROGRAM_TYPE_SPORTS = 3, + NRSC5_PROGRAM_TYPE_TALK = 4, + NRSC5_PROGRAM_TYPE_ROCK = 5, + NRSC5_PROGRAM_TYPE_CLASSIC_ROCK = 6, + NRSC5_PROGRAM_TYPE_ADULT_HITS = 7, + NRSC5_PROGRAM_TYPE_SOFT_ROCK = 8, + NRSC5_PROGRAM_TYPE_TOP_40 = 9, + NRSC5_PROGRAM_TYPE_COUNTRY = 10, + NRSC5_PROGRAM_TYPE_OLDIES = 11, + NRSC5_PROGRAM_TYPE_SOFT = 12, + NRSC5_PROGRAM_TYPE_NOSTALGIA = 13, + NRSC5_PROGRAM_TYPE_JAZZ = 14, + NRSC5_PROGRAM_TYPE_CLASSICAL = 15, + NRSC5_PROGRAM_TYPE_RHYTHM_AND_BLUES = 16, + NRSC5_PROGRAM_TYPE_SOFT_RHYTHM_AND_BLUES = 17, + NRSC5_PROGRAM_TYPE_FOREIGN_LANGUAGE = 18, + NRSC5_PROGRAM_TYPE_RELIGIOUS_MUSIC = 19, + NRSC5_PROGRAM_TYPE_RELIGIOUS_TALK = 20, + NRSC5_PROGRAM_TYPE_PERSONALITY = 21, + NRSC5_PROGRAM_TYPE_PUBLIC = 22, + NRSC5_PROGRAM_TYPE_COLLEGE = 23, + NRSC5_PROGRAM_TYPE_SPANISH_TALK = 24, + NRSC5_PROGRAM_TYPE_SPANISH_MUSIC = 25, + NRSC5_PROGRAM_TYPE_HIP_HOP = 26, + NRSC5_PROGRAM_TYPE_WEATHER = 29, + NRSC5_PROGRAM_TYPE_EMERGENCY_TEST = 30, + NRSC5_PROGRAM_TYPE_EMERGENCY = 31, + NRSC5_PROGRAM_TYPE_TRAFFIC = 65, + NRSC5_PROGRAM_TYPE_SPECIAL_READING_SERVICES = 76 +}; + +/** + * Station Information Service *Audio* service descriptor. This is a + * linked list element that may point to further audio service + * descriptor elements. Refer to NRSC-5 document SY_IDD_1020s. + */ +struct nrsc5_sis_asd_t +{ + struct nrsc5_sis_asd_t *next; /**< Pointer to next element or NULL */ + unsigned int program; /**< program number 0, 1, ..., 7 */ + unsigned int access; /**< NRSC5_ACCESS_PUBLIC or NRSC5_ACCESS_RESTRICTED */ + unsigned int type; /**< audio service, e.g. NRSC5_PROGRAM_TYPE_JAZZ */ + unsigned int sound_exp; /**< 0 is none, 2 is Dolby Pro Logic II Surround */ +}; +/** + * Defines a typename for struct nrsc5_sis_asd_t + */ +typedef struct nrsc5_sis_asd_t nrsc5_sis_asd_t; + +enum +{ + NRSC5_SERVICE_DATA_TYPE_NON_SPECIFIC = 0, + NRSC5_SERVICE_DATA_TYPE_NEWS = 1, + NRSC5_SERVICE_DATA_TYPE_SPORTS = 3, + NRSC5_SERVICE_DATA_TYPE_WEATHER = 29, + NRSC5_SERVICE_DATA_TYPE_EMERGENCY = 31, + NRSC5_SERVICE_DATA_TYPE_TRAFFIC = 65, + NRSC5_SERVICE_DATA_TYPE_IMAGE_MAPS = 66, + NRSC5_SERVICE_DATA_TYPE_TEXT = 80, + NRSC5_SERVICE_DATA_TYPE_ADVERTISING = 256, + NRSC5_SERVICE_DATA_TYPE_FINANCIAL = 257, + NRSC5_SERVICE_DATA_TYPE_STOCK_TICKER = 258, + NRSC5_SERVICE_DATA_TYPE_NAVIGATION = 259, + NRSC5_SERVICE_DATA_TYPE_ELECTRONIC_PROGRAM_GUIDE = 260, + NRSC5_SERVICE_DATA_TYPE_AUDIO = 261, + NRSC5_SERVICE_DATA_TYPE_PRIVATE_DATA_NETWORK = 262, + NRSC5_SERVICE_DATA_TYPE_SERVICE_MAINTENANCE = 263, + NRSC5_SERVICE_DATA_TYPE_HD_RADIO_SYSTEM_SERVICES = 264, + NRSC5_SERVICE_DATA_TYPE_AUDIO_RELATED_DATA = 265 +}; + +/** + * Station Information Service *Data* service descriptor. This is a + * linked list element that may point to further data service descriptors + * via `next` member if not `NULL`. See + * nrsc5_service_data_type_name() for named types of data. + * Refer to NRSC-5 document SY_IDD_1020s. + */ +struct nrsc5_sis_dsd_t +{ + struct nrsc5_sis_dsd_t *next; /**< Pointer to next element or NULL */ + unsigned int access; /**< NRSC5_ACCESS_PUBLIC or NRSC5_ACCESS_RESTRICTED */ + unsigned int type; /**< data service type, e.g. NRSC5_SERVICE_DATA_TYPE_TEXT */ + uint32_t mime_type; /**< MIME type, e.g. `NRSC5_MIME_TEXT` */ +}; +/** + * Defines a typename for struct nrsc5_sis_dsd_t + */ +typedef struct nrsc5_sis_dsd_t nrsc5_sis_dsd_t; + +/** Incoming event from receiver. + * + * This event structure is passed to your application supplied + * callback function--see nrsc5_set_callback(). It is a union of + * various structs, keyed by member `event`. + */ +struct nrsc5_event_t +{ +/*! Type of event. + * The member `event` determines which sort of event occurred: + * - `NRSC5_EVENT_LOST_DEVICE` : signal is over + * - `NRSC5_EVENT_BER` : Bit Error Ratio data, see the `ber` union member + * - `NRSC5_EVENT_MER` : modulation error ratio, see the `mer` union member, + * and NRSC5 document SY_TN_2646s + * - `NRSC5_EVENT_IQ` : IQ data, see the `iq` union member + * - `NRSC5_EVENT_HD`C : HDC audio packet, see the `hdc` union member + * - `NRSC5_EVENT_AUDIO` : audio buffer, see the `audio` union member + * - `NRSC5_EVENT_SYNC` : indicates synchronization achieved + * - `NRSC5_EVENT_LOST_SYNC` : indicates synchronization lost + * - `NRSC5_EVENT_ID3` : ID3 information packet arrived, see `id3` member + * and information in HD-Radio document SY_IDD_1028s. + * - `NRSC5_EVENT_SIG` : service information arrived, see `sig` member + * - `NRSC5_EVENT_LOT` : LOT file data available, see `lot` member + * - `NRSC5_EVENT_SIS` : station information, see `sis` member + */ + unsigned int event; + union + { + struct { + const void *data; + size_t count; + } iq; + struct { + float cber; + } ber; + struct { + float lower; + float upper; + } mer; + struct { + unsigned int program; + const uint8_t *data; + size_t count; + } hdc; + struct { + unsigned int program; + const int16_t *data; + size_t count; + } audio; + struct { + unsigned int program; + const char *title; + const char *artist; + const char *album; + const char *genre; + struct { + unsigned int size; + const uint8_t* data; + } raw; + struct { + const char *owner; + const char *id; + } ufid; + struct { + uint32_t mime; + int param; + int lot; + } xhdr; + } id3; + struct { + uint16_t port; + unsigned int lot; + unsigned int size; + uint32_t mime; + const char *name; + const uint8_t *data; + } lot; + struct { + nrsc5_sig_service_t *services; + } sig; + struct { + const char *country_code; + int fcc_facility_id; + const char *name; + const char *slogan; + const char *message; + const char *alert; + float latitude; + float longitude; + int altitude; + nrsc5_sis_asd_t *audio_services; + nrsc5_sis_dsd_t *data_services; + } sis; + }; +}; +/** + * Defines a typename for struct nrsc5_event_t + */ +typedef struct nrsc5_event_t nrsc5_event_t; + +/** + * Prototype for a user event handling callback function. + * The function will be invoked with a pointer to the nrsc5_event_t + * and a pointer to a second contextual argument, established by + * nrsc5_set_callback(). + */ +typedef void (*nrsc5_callback_t)(const nrsc5_event_t *evt, void *opaque); + +/** + * An opaque data type used by API functions to represent session information. + * Applications should acquire a pointer to one via the `open_` functions: + * nrsc5_open(), nrsc5_open_file(), nrsc5_open_pipe(), or nrsc5_open_rtltcp(). + */ +typedef struct nrsc5_t nrsc5_t; + + +/* ============================================================================ + * Public functions. All functions return void or an error code (0 == success). + */ + +/** + * Retrieves a string corresponding to a service data type. + * @param[in] type a service data type integer. + * @param[out] name character pointer to a string naming the service type + * @return Nothing is returned + * + * This name will be quite short, e.g. "News" or "Weather". If the type is + * not recognized, it will the string "Unknown". + */ +void nrsc5_service_data_type_name(unsigned int type, const char **name); + +/** + * Retrieves a string corresponding to a program type. + * @param[in] type a protram data type integer. + * @param[out] name character pointer to a string naming the service type + * @return Nothing is returned + * + * This name will be quite short, e.g. "News" or "Rock". If the type is + * not recognized, it will the string "Unknown". + */ +void nrsc5_program_type_name(unsigned int type, const char **name); + +/** + * Initializes a session for use with a pipe. + * @param[out] st handle for an `nrsc5_t` + * @return 0 on success, nonzero on error + * + */ +int nrsc5_open_pipe(nrsc5_t **st ); + +/** + * Closes an nrsc5 session. + * @param[in] st pointer to an `nrsc5_t` + * @return Nothing is returned. + * + * Any worker thread is signalled to exit, files and sockets are closed, + * and I/O buffers are freed. + */ +void nrsc5_close(nrsc5_t *); + +/** + * Set the session mode to AM or FM. + * @param[in] st pointer to an `nrsc5_t` session object + * @param[in] mode either `NRSC5_MODE_FM` or `NRSC5_MODE_AM` + * @return 0 on success or nonzero on error. + * + */ +int nrsc5_set_mode(nrsc5_t *, int mode); + +/** + * Establish a callback function. + * + * @param[in] st pointer to an `nrsc5_t` session object + * @param[in] callback pointer to an event handling function of two arguments + * @param[in] opaque pointer to the function's intended 2nd argument + * @return Nothing is returned. + * + */ +void nrsc5_set_callback(nrsc5_t *st, nrsc5_callback_t callback, void *opaque); + + +/** + * Push an IQ array of 8-bit unsigned samples into the demodulator. + * + * @param[in] st pointer to an `nrsc5_t` session object + * @param[in] samples pointer to an array 8-bit unsigned samples + * @param[in] length the number of samples in the array + * @return 0 on success, nonzero on error + * + */ +int nrsc5_pipe_samples_cu8(nrsc5_t *st, uint8_t *samples, unsigned int length); + + +/** + * Push an IQ input array of 16-bit signed samples into the demodulator. + * + * @param[in] st pointer to an `nrsc5_t` session object + * @param[in] samples pointer to an array 16-bit signed samples + * @param[in] length the number of samples in the array + * @return 0 on success, nonzero on error + * + */ +int nrsc5_pipe_samples_cs16(nrsc5_t *st, int16_t *samples, unsigned int length); + +#ifdef __cplusplus +} +#endif + +#endif /* NRSC5_H_ */ + diff --git a/src/hddsp/output.c b/src/hddsp/output.c index 0d86e72..5a2fd20 100644 --- a/src/hddsp/output.c +++ b/src/hddsp/output.c @@ -1,679 +1,679 @@ -/* - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "config.h" - -#include -#include -#include -#include -#ifndef _WIN32 -#include -#endif - -#include "defines.h" -#include "output.h" -#include "private.h" -#include "unicode.h" - -void output_push(output_t *st, uint8_t *pkt, unsigned int len, unsigned int program, unsigned int stream_id) -{ - nrsc5_report_hdc(st->radio, program, pkt, len); - - if (stream_id != 0) - return; // TODO: Process enhanced stream - -#ifdef USE_FAAD2 - void *buffer; - NeAACDecFrameInfo info; - - if (!st->aacdec[program]) - { - unsigned long samprate = 22050; - NeAACDecInitHDC(&st->aacdec[program], &samprate); - } - - buffer = NeAACDecDecode(st->aacdec[program], &info, pkt, len); - if (info.error > 0) - log_error("Decode error: %s", NeAACDecGetErrorMessage(info.error)); - - if (info.error == 0 && info.samples > 0) - nrsc5_report_audio(st->radio, program, buffer, info.samples); -#endif -} - -static void aas_free_lot(aas_file_t *file) -{ - free(file->name); - if (file->fragments) - { - for (int i = 0; i < MAX_LOT_FRAGMENTS; i++) - free(file->fragments[i]); - free(file->fragments); - } - memset(file, 0, sizeof(*file)); -} - -static void aas_reset(output_t *st) -{ - for (int i = 0; i < MAX_PORTS; i++) - { - aas_port_t *port = &st->ports[i]; - if (port->port == 0) - continue; - switch (port->type) - { - case AAS_TYPE_STREAM: - free(port->stream.data); - break; - case AAS_TYPE_LOT: - for (int j = 0; j < MAX_LOT_FILES; j++) - aas_free_lot(&port->lot.files[j]); - break; - } - } - - for (int i = 0; i < MAX_SIG_SERVICES; i++) - { - free(st->services[i].name); - } - - memset(st->ports, 0, sizeof(st->ports)); - memset(st->services, 0, sizeof(st->services)); -} - -void output_reset(output_t *st) -{ - aas_reset(st); - -#ifdef USE_FAAD2 - for (int i = 0; i < MAX_PROGRAMS; i++) - { - if (st->aacdec[i]) - NeAACDecClose(st->aacdec[i]); - st->aacdec[i] = NULL; - } -#endif -} - -void output_init(output_t *st, nrsc5_t *radio) -{ - st->radio = radio; -#ifdef USE_FAAD2 - for (int i = 0; i < MAX_PROGRAMS; i++) - st->aacdec[i] = NULL; -#endif - - memset(st->ports, 0, sizeof(st->ports)); - memset(st->services, 0, sizeof(st->services)); - - output_reset(st); -} - -void output_free(output_t *st) -{ - output_reset(st); -} - -static unsigned int id3_length(uint8_t *buf) -{ - return ((buf[0] & 0x7f) << 21) | ((buf[1] & 0x7f) << 14) | ((buf[2] & 0x7f) << 7) | (buf[3] & 0x7f); -} - -static char *id3_text(uint8_t *buf, unsigned int frame_len) -{ - char *text; - - if (frame_len > 0) - { - if (buf[0] == 0) - return iso_8859_1_to_utf_8(buf + 1, frame_len - 1); - else if (buf[0] == 1) - return ucs_2_to_utf_8(buf + 1, frame_len - 1); - else - log_warn("Invalid encoding: %d", buf[0]); - } - - text = malloc(1); - text[0] = 0; - return text; -} - -static void output_id3(output_t *st, unsigned int program, uint8_t *buf, unsigned int len) -{ - char *title = NULL, *artist = NULL, *album = NULL, *genre = NULL, *ufid_owner = NULL, *ufid_id = NULL; - uint32_t xhdr_mime = 0; - int xhdr_param = -1, xhdr_lot = -1; - - unsigned int off = 0, id3_len; - nrsc5_event_t evt; - - evt.event = NRSC5_EVENT_ID3; - - if (len < 10 || memcmp(buf, "ID3\x03\x00", 5) || buf[5]) return; - id3_len = id3_length(buf + 6) + 10; - if (id3_len > len) return; - off += 10; - - while (off + 10 <= id3_len) - { - uint8_t *tag = buf + off; - uint8_t *data = tag + 10; - unsigned int frame_len = id3_length(tag + 4); - if (off + 10 + frame_len > id3_len) - break; - - if (memcmp(tag, "TIT2", 4) == 0) - { - free(title); - title = id3_text(data, frame_len); - } - else if (memcmp(tag, "TPE1", 4) == 0) - { - free(artist); - artist = id3_text(data, frame_len); - } - else if (memcmp(tag, "TALB", 4) == 0) - { - free(album); - album = id3_text(data, frame_len); - } - else if (memcmp(tag, "TCON", 4) == 0) - { - free(genre); - genre = id3_text(data, frame_len); - } - else if (memcmp(tag, "UFID", 4) == 0) - { - uint8_t *delim = memchr(data, 0, frame_len); - uint8_t *end = data + frame_len; - - if (delim) - { - free(ufid_owner); - ufid_owner = strdup((char *)data); - - free(ufid_id); - ufid_id = strndup((char *)delim + 1, end - delim - 1); - } - } - else if (memcmp(tag, "COMR", 4) == 0) - { - int i; - uint8_t *delim[4]; - uint8_t *pos = data + 1; - uint8_t *end = data + frame_len; - - char *price, until[11], *url, *seller, *desc; - int received_as; - - for (i = 0; i < 4; i++) - { - if (pos >= end) - break; - if ((delim[i] = memchr(pos, 0, end - pos)) == NULL) - break; - - pos = delim[i] + 1; - if (i == 0) - pos += 8; - else if (i == 1) - pos += 1; - } - - if (i == 4) - { - price = (char *) data + 1; - sprintf(until, "%.4s-%.2s-%.2s", delim[0] + 1, delim[0] + 5, delim[0] + 7); - url = (char *) delim[0] + 9; - received_as = *(delim[1] + 1); - seller = (char *) delim[1] + 2; - desc = (char *) delim[2] + 1; - log_debug("Commercial: price=%s until=%s url=\"%s\" seller=\"%s\" desc=\"%s\" received_as=%d", - price, until, url, seller, desc, received_as); - } - } - else if (memcmp(tag, "XHDR", 4) == 0) - { - uint8_t extlen; - - if (frame_len < 6) - { - log_warn("bad XHDR tag (frame_len %d)", frame_len); - } - else - { - xhdr_mime = data[0] | (data[1] << 8) | (data[2] << 16) | ((uint32_t)data[3] << 24); - xhdr_param = data[4]; - extlen = data[5]; - if (6u + extlen != frame_len) - log_warn("bad XHDR tag (frame_len %d, extlen %d)", frame_len, extlen); - else if (xhdr_param == 0 && extlen == 2) - xhdr_lot = data[6] | (data[7] << 8); - else if (xhdr_param == 1 && extlen == 0) - xhdr_lot = -1; - else - log_warn("unhandled XHDR param (frame_len %d, param %d, extlen %d)", frame_len, xhdr_param, extlen); - } - } - else - { - unsigned int i; - char *hex = malloc(3 * frame_len + 1); - for (i = 0; i < frame_len; i++) - sprintf(hex + (3 * i), "%02X ", buf[off + 10 + i]); - hex[3 * i - 1] = 0; - log_debug("%c%c%c%c tag: %s", buf[off], buf[off+1], buf[off+2], buf[off+3], hex); - free(hex); - } - - off += 10 + frame_len; - } - - evt.id3.program = program; - evt.id3.title = title; - evt.id3.artist = artist; - evt.id3.album = album; - evt.id3.genre = genre; - evt.id3.raw.data = buf; - evt.id3.raw.size = len; - evt.id3.ufid.owner = ufid_owner; - evt.id3.ufid.id = ufid_id; - evt.id3.xhdr.mime = xhdr_mime; - evt.id3.xhdr.param = xhdr_param; - evt.id3.xhdr.lot = xhdr_lot; - - nrsc5_report(st->radio, &evt); - - free(title); - free(artist); - free(album); - free(genre); - free(ufid_owner); - free(ufid_id); -} - -static void parse_sig(output_t *st, uint8_t *buf, unsigned int len) -{ - int port_idx = 0, service_idx = 0, component_idx = 0; - uint8_t *p = buf; - sig_service_t *service = NULL; - - if (st->services[0].type != SIG_SERVICE_NONE) - { - // We assume that the SIG will never change, and only process it once. - return; - } - - memset(st->ports, 0, sizeof(st->ports)); - memset(st->services, 0, sizeof(st->services)); - - while (p < buf + len) - { - uint8_t type = *p++; - switch (type & 0xF0) - { - case 0x40: - { - if (service_idx == MAX_SIG_SERVICES) - { - log_warn("Too many SIG services"); - goto done; - } - - service = &st->services[service_idx++]; - service->type = type == 0x40 ? SIG_SERVICE_AUDIO : SIG_SERVICE_DATA; - service->number = p[0] | (p[1] << 8); - component_idx = 0; - - p += 3; - break; - } - case 0x60: - { - // length (1-byte) value (length - 1) - uint8_t l = *p++; - if (service == NULL) - { - log_warn("Invalid SIG data (%02X)", type); - goto done; - } - else if (type == 0x69) - { - char *name = malloc(l - 1); - memcpy(name, p + 1, l - 2); - name[l - 2] = 0; - service->name = name; - } - else if (type == 0x67) - { - sig_component_t *comp; - - if (component_idx == MAX_SIG_COMPONENTS) - { - log_warn("Too many SIG components"); - goto done; - } - - if (port_idx == MAX_PORTS) - { - log_warn("Too many AAS ports"); - goto done; - } - - comp = &service->component[component_idx++]; - comp->type = SIG_COMPONENT_DATA; - comp->id = p[0]; - comp->data.port = p[1] | (p[2] << 8); - comp->data.service_data_type = p[3] | (p[4] << 8); - comp->data.type = p[5]; - comp->data.mime = p[8] | (p[9] << 8) | (p[10] << 16) | ((uint32_t)p[11] << 24); - - aas_port_t *port = &st->ports[port_idx++]; - port->port = comp->data.port; - port->type = comp->data.type; - port->mime = comp->data.mime; - port->service_number = service->number; - } - else if (type == 0x66) - { - sig_component_t *comp; - - if (component_idx == MAX_SIG_COMPONENTS) - { - log_warn("Too many SIG components"); - goto done; - } - - comp = &service->component[component_idx++]; - comp->type = SIG_COMPONENT_AUDIO; - comp->id = p[0]; - comp->audio.port = p[1]; - comp->audio.type = p[2]; - comp->audio.mime = p[7] | (p[8] << 8) | (p[9] << 16) | ((uint32_t)p[10] << 24); - } - p += l - 1; - break; - } - default: - log_warn("unexpected byte %02X", *p); - goto done; - } - } - -done: - nrsc5_report_sig(st->radio, st->services, service_idx); -} - -static aas_port_t *find_port(output_t *st, uint16_t port_id) -{ - unsigned int i; - for (i = 0; i < MAX_PORTS; i++) - { - if (st->ports[i].port == port_id) - return &st->ports[i]; - } - return NULL; -} - -static aas_file_t *find_lot(aas_port_t *port, unsigned int lot) -{ - for (int i = 0; i < MAX_LOT_FILES; i++) - { - if (port->lot.files[i].timestamp == 0) - continue; - if (port->lot.files[i].lot == lot) - return &port->lot.files[i]; - } - return NULL; -} - -static aas_file_t *find_free_lot(aas_port_t *port) -{ - unsigned int min_timestamp = UINT_MAX; - unsigned int min_idx = 0; - aas_file_t *file; - - for (int i = 0; i < MAX_LOT_FILES; i++) - { - unsigned int timestamp = port->lot.files[i].timestamp; - if (timestamp == 0) - return &port->lot.files[i]; - if (timestamp < min_timestamp) - { - min_timestamp = timestamp; - min_idx = i; - } - } - - file = &port->lot.files[min_idx]; - aas_free_lot(file); - return file; -} - -static void process_port(output_t *st, uint16_t port_id, uint8_t *buf, unsigned int len) -{ - static unsigned int counter = 1; - aas_port_t *port; - - if (st->services[0].type == SIG_SERVICE_NONE) - { - // Wait until we receive SIG data. - return; - } - - port = find_port(st, port_id); - if (port == NULL) - { - log_debug("missing port %04X", port_id); - return; - } - - switch (port->type) - { - case AAS_TYPE_STREAM: - { - uint8_t frame_type; - - if (port->stream.data == NULL) - port->stream.data = malloc(MAX_STREAM_BYTES); - - if (port->mime == NRSC5_MIME_HERE_IMAGE) - frame_type = 0xF7; - else - frame_type = 0x0F; - - while (len) - { - uint8_t x = *buf++; - len--; - - // Wait until we find start of a packet. This is either: - // - FF 0F - // - FF F7 FF F7 - if (port->stream.prev[0] == 0xFF && x == frame_type && - (frame_type != 0xF7 || (port->stream.prev[1] == frame_type && port->stream.prev[2] == 0xFF))) - { - if (port->stream.type != 0 && port->stream.idx > 0) - { - port->stream.idx--; - log_debug("Stream data: port=%04X type=%d size=%d size2=%d", port_id, port->stream.type, port->stream.idx, (port->stream.data[0] << 8) | port->stream.data[1]); - } - port->stream.idx = 0; - port->stream.prev[0] = 0; - port->stream.prev[1] = 0; - port->stream.prev[2] = 0; - port->stream.type = x; - } - else - { - if (port->stream.type != 0) - port->stream.data[port->stream.idx++] = x; - port->stream.prev[2] = port->stream.prev[1]; - port->stream.prev[1] = port->stream.prev[0]; - port->stream.prev[0] = x; - } - - if (port->stream.idx == MAX_STREAM_BYTES) - { - log_info("stream packet overflow (%04X)", port_id); - port->stream.type = 0; - } - } - break; - } - case AAS_TYPE_PACKET: - { - if (len < 4) - { - log_warn("bad packet (port %04X, len %d)", port_id, len); - break; - } - log_debug("Packet data: port=%04X size=%d", port_id, len); - break; - } - case AAS_TYPE_LOT: - { - if (len < 8) - { - log_warn("bad fragment (port %04X, len %d)", port_id, len); - return; - } - uint8_t hdrlen = buf[0]; - uint16_t lot = buf[2] | (buf[3] << 8); - uint32_t seq = buf[4] | (buf[5] << 8) | (buf[6] << 16) | ((uint32_t)buf[7] << 24); - if (hdrlen < 8 || hdrlen > len) - { - log_warn("wrong header len (port %04X, len %d, hdrlen %d)", port_id, len, hdrlen); - return; - } - buf += 8; - len -= 8; - hdrlen -= 8; - - if (seq >= MAX_LOT_FRAGMENTS) - { - log_warn("sequence too large (%d)", seq); - return; - } - - aas_file_t *file = find_lot(port, lot); - if (file == NULL) - { - file = find_free_lot(port); - file->lot = lot; - file->fragments = calloc(MAX_LOT_FRAGMENTS, sizeof(uint8_t*)); - } - file->timestamp = counter++; - - if (seq == 0) - { - if (hdrlen < 16) - { - log_warn("header is too short (port %04X, len %d, hdrlen %d)", port_id, len, hdrlen); - return; - } - - // uint32_t == 1 - // uint32_t xxx - // uint32_t size - // uint32_t mimeHash - file->size = buf[8] | (buf[9] << 8) | (buf[10] << 16) | ((uint32_t)buf[11] << 24); - file->mime = buf[12] | (buf[13] << 8) | (buf[14] << 16) | ((uint32_t)buf[15] << 24); - buf += 16; - len -= 16; - hdrlen -= 16; - - // Everything after the fixed header is the filename. - free(file->name); - file->name = strndup((const char *)buf, hdrlen); - buf += hdrlen; - len -= hdrlen; - hdrlen = 0; - - log_debug("File %s, size %d, lot %d, port %04X, mime %08X", file->name, file->size, file->lot, port->port, file->mime); - } - - if (hdrlen != 0) - { - log_warn("unexpected hdrlen (port %04X, hdrlen %d)", port_id, hdrlen); - break; - } - - if (!file->fragments[seq]) - { - uint8_t *fragment = calloc(LOT_FRAGMENT_SIZE, 1); - if (len > LOT_FRAGMENT_SIZE) - { - log_warn("fragment too large (%d)", len); - break; - } - memcpy(fragment, buf, len); - file->fragments[seq] = fragment; - } - - if (file->size) - { - int complete = 1; - int num_fragments = (file->size + LOT_FRAGMENT_SIZE - 1) / LOT_FRAGMENT_SIZE; - for (int i = 0; i < num_fragments; i++) - { - if (file->fragments[i] == NULL) - { - complete = 0; - break; - } - } - if (complete) - { - uint8_t *data = malloc(num_fragments * LOT_FRAGMENT_SIZE); - for (int i = 0; i < num_fragments; i++) - memcpy(data + i * LOT_FRAGMENT_SIZE, file->fragments[i], LOT_FRAGMENT_SIZE); - nrsc5_report_lot(st->radio, port->port, file->lot, file->size, file->mime, file->name, data); - free(data); - aas_free_lot(file); - } - } - break; - } - default: - log_info("unknown port type %d", port->type); - break; - } -} - -void output_aas_push(output_t *st, uint8_t *buf, unsigned int len) -{ - uint16_t port = buf[0] | (buf[1] << 8); - uint16_t seq = buf[2] | (buf[3] << 8); - if (port == 0x5100 || (port >= 0x5201 && port <= 0x5207)) - { - // PSD ports - output_id3(st, port & 0x7, buf + 4, len - 4); - } - else if (port == 0x20) - { - // Station Information Guide - parse_sig(st, buf + 4, len - 4); - } - else if (port >= 0x401 && port <= 0x50FF) - { - process_port(st, port, buf + 4, len - 4); - } - else - { - log_warn("unknown AAS port %04X, seq %04X, length %d", port, seq, len); - } -} +/* + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include +#include +#include +#ifndef _WIN32 +#include +#endif + +#include "defines.h" +#include "output.h" +#include "private.h" +#include "unicode.h" + +void output_push(output_t *st, uint8_t *pkt, unsigned int len, unsigned int program, unsigned int stream_id) +{ + nrsc5_report_hdc(st->radio, program, pkt, len); + + if (stream_id != 0) + return; // TODO: Process enhanced stream + +#ifdef USE_FAAD2 + void *buffer; + NeAACDecFrameInfo info; + + if (!st->aacdec[program]) + { + unsigned long samprate = 22050; + NeAACDecInitHDC(&st->aacdec[program], &samprate); + } + + buffer = NeAACDecDecode(st->aacdec[program], &info, pkt, len); + if (info.error > 0) + log_error("Decode error: %s", NeAACDecGetErrorMessage(info.error)); + + if (info.error == 0 && info.samples > 0) + nrsc5_report_audio(st->radio, program, buffer, info.samples); +#endif +} + +static void aas_free_lot(aas_file_t *file) +{ + free(file->name); + if (file->fragments) + { + for (int i = 0; i < MAX_LOT_FRAGMENTS; i++) + free(file->fragments[i]); + free(file->fragments); + } + memset(file, 0, sizeof(*file)); +} + +static void aas_reset(output_t *st) +{ + for (int i = 0; i < MAX_PORTS; i++) + { + aas_port_t *port = &st->ports[i]; + if (port->port == 0) + continue; + switch (port->type) + { + case AAS_TYPE_STREAM: + free(port->stream.data); + break; + case AAS_TYPE_LOT: + for (int j = 0; j < MAX_LOT_FILES; j++) + aas_free_lot(&port->lot.files[j]); + break; + } + } + + for (int i = 0; i < MAX_SIG_SERVICES; i++) + { + free(st->services[i].name); + } + + memset(st->ports, 0, sizeof(st->ports)); + memset(st->services, 0, sizeof(st->services)); +} + +void output_reset(output_t *st) +{ + aas_reset(st); + +#ifdef USE_FAAD2 + for (int i = 0; i < MAX_PROGRAMS; i++) + { + if (st->aacdec[i]) + NeAACDecClose(st->aacdec[i]); + st->aacdec[i] = NULL; + } +#endif +} + +void output_init(output_t *st, nrsc5_t *radio) +{ + st->radio = radio; +#ifdef USE_FAAD2 + for (int i = 0; i < MAX_PROGRAMS; i++) + st->aacdec[i] = NULL; +#endif + + memset(st->ports, 0, sizeof(st->ports)); + memset(st->services, 0, sizeof(st->services)); + + output_reset(st); +} + +void output_free(output_t *st) +{ + output_reset(st); +} + +static unsigned int id3_length(uint8_t *buf) +{ + return ((buf[0] & 0x7f) << 21) | ((buf[1] & 0x7f) << 14) | ((buf[2] & 0x7f) << 7) | (buf[3] & 0x7f); +} + +static char *id3_text(uint8_t *buf, unsigned int frame_len) +{ + char *text; + + if (frame_len > 0) + { + if (buf[0] == 0) + return iso_8859_1_to_utf_8(buf + 1, frame_len - 1); + else if (buf[0] == 1) + return ucs_2_to_utf_8(buf + 1, frame_len - 1); + else + log_warn("Invalid encoding: %d", buf[0]); + } + + text = malloc(1); + text[0] = 0; + return text; +} + +static void output_id3(output_t *st, unsigned int program, uint8_t *buf, unsigned int len) +{ + char *title = NULL, *artist = NULL, *album = NULL, *genre = NULL, *ufid_owner = NULL, *ufid_id = NULL; + uint32_t xhdr_mime = 0; + int xhdr_param = -1, xhdr_lot = -1; + + unsigned int off = 0, id3_len; + nrsc5_event_t evt; + + evt.event = NRSC5_EVENT_ID3; + + if (len < 10 || memcmp(buf, "ID3\x03\x00", 5) || buf[5]) return; + id3_len = id3_length(buf + 6) + 10; + if (id3_len > len) return; + off += 10; + + while (off + 10 <= id3_len) + { + uint8_t *tag = buf + off; + uint8_t *data = tag + 10; + unsigned int frame_len = id3_length(tag + 4); + if (off + 10 + frame_len > id3_len) + break; + + if (memcmp(tag, "TIT2", 4) == 0) + { + free(title); + title = id3_text(data, frame_len); + } + else if (memcmp(tag, "TPE1", 4) == 0) + { + free(artist); + artist = id3_text(data, frame_len); + } + else if (memcmp(tag, "TALB", 4) == 0) + { + free(album); + album = id3_text(data, frame_len); + } + else if (memcmp(tag, "TCON", 4) == 0) + { + free(genre); + genre = id3_text(data, frame_len); + } + else if (memcmp(tag, "UFID", 4) == 0) + { + uint8_t *delim = memchr(data, 0, frame_len); + uint8_t *end = data + frame_len; + + if (delim) + { + free(ufid_owner); + ufid_owner = strdup((char *)data); + + free(ufid_id); + ufid_id = strndup((char *)delim + 1, end - delim - 1); + } + } + else if (memcmp(tag, "COMR", 4) == 0) + { + int i; + uint8_t *delim[4]; + uint8_t *pos = data + 1; + uint8_t *end = data + frame_len; + + char *price, until[11], *url, *seller, *desc; + int received_as; + + for (i = 0; i < 4; i++) + { + if (pos >= end) + break; + if ((delim[i] = memchr(pos, 0, end - pos)) == NULL) + break; + + pos = delim[i] + 1; + if (i == 0) + pos += 8; + else if (i == 1) + pos += 1; + } + + if (i == 4) + { + price = (char *) data + 1; + sprintf(until, "%.4s-%.2s-%.2s", delim[0] + 1, delim[0] + 5, delim[0] + 7); + url = (char *) delim[0] + 9; + received_as = *(delim[1] + 1); + seller = (char *) delim[1] + 2; + desc = (char *) delim[2] + 1; + log_debug("Commercial: price=%s until=%s url=\"%s\" seller=\"%s\" desc=\"%s\" received_as=%d", + price, until, url, seller, desc, received_as); + } + } + else if (memcmp(tag, "XHDR", 4) == 0) + { + uint8_t extlen; + + if (frame_len < 6) + { + log_warn("bad XHDR tag (frame_len %d)", frame_len); + } + else + { + xhdr_mime = data[0] | (data[1] << 8) | (data[2] << 16) | ((uint32_t)data[3] << 24); + xhdr_param = data[4]; + extlen = data[5]; + if (6u + extlen != frame_len) + log_warn("bad XHDR tag (frame_len %d, extlen %d)", frame_len, extlen); + else if (xhdr_param == 0 && extlen == 2) + xhdr_lot = data[6] | (data[7] << 8); + else if (xhdr_param == 1 && extlen == 0) + xhdr_lot = -1; + else + log_warn("unhandled XHDR param (frame_len %d, param %d, extlen %d)", frame_len, xhdr_param, extlen); + } + } + else + { + unsigned int i; + char *hex = malloc(3 * frame_len + 1); + for (i = 0; i < frame_len; i++) + sprintf(hex + (3 * i), "%02X ", buf[off + 10 + i]); + hex[3 * i - 1] = 0; + log_debug("%c%c%c%c tag: %s", buf[off], buf[off+1], buf[off+2], buf[off+3], hex); + free(hex); + } + + off += 10 + frame_len; + } + + evt.id3.program = program; + evt.id3.title = title; + evt.id3.artist = artist; + evt.id3.album = album; + evt.id3.genre = genre; + evt.id3.raw.data = buf; + evt.id3.raw.size = len; + evt.id3.ufid.owner = ufid_owner; + evt.id3.ufid.id = ufid_id; + evt.id3.xhdr.mime = xhdr_mime; + evt.id3.xhdr.param = xhdr_param; + evt.id3.xhdr.lot = xhdr_lot; + + nrsc5_report(st->radio, &evt); + + free(title); + free(artist); + free(album); + free(genre); + free(ufid_owner); + free(ufid_id); +} + +static void parse_sig(output_t *st, uint8_t *buf, unsigned int len) +{ + int port_idx = 0, service_idx = 0, component_idx = 0; + uint8_t *p = buf; + sig_service_t *service = NULL; + + if (st->services[0].type != SIG_SERVICE_NONE) + { + // We assume that the SIG will never change, and only process it once. + return; + } + + memset(st->ports, 0, sizeof(st->ports)); + memset(st->services, 0, sizeof(st->services)); + + while (p < buf + len) + { + uint8_t type = *p++; + switch (type & 0xF0) + { + case 0x40: + { + if (service_idx == MAX_SIG_SERVICES) + { + log_warn("Too many SIG services"); + goto done; + } + + service = &st->services[service_idx++]; + service->type = type == 0x40 ? SIG_SERVICE_AUDIO : SIG_SERVICE_DATA; + service->number = p[0] | (p[1] << 8); + component_idx = 0; + + p += 3; + break; + } + case 0x60: + { + // length (1-byte) value (length - 1) + uint8_t l = *p++; + if (service == NULL) + { + log_warn("Invalid SIG data (%02X)", type); + goto done; + } + else if (type == 0x69) + { + char *name = malloc(l - 1); + memcpy(name, p + 1, l - 2); + name[l - 2] = 0; + service->name = name; + } + else if (type == 0x67) + { + sig_component_t *comp; + + if (component_idx == MAX_SIG_COMPONENTS) + { + log_warn("Too many SIG components"); + goto done; + } + + if (port_idx == MAX_PORTS) + { + log_warn("Too many AAS ports"); + goto done; + } + + comp = &service->component[component_idx++]; + comp->type = SIG_COMPONENT_DATA; + comp->id = p[0]; + comp->data.port = p[1] | (p[2] << 8); + comp->data.service_data_type = p[3] | (p[4] << 8); + comp->data.type = p[5]; + comp->data.mime = p[8] | (p[9] << 8) | (p[10] << 16) | ((uint32_t)p[11] << 24); + + aas_port_t *port = &st->ports[port_idx++]; + port->port = comp->data.port; + port->type = comp->data.type; + port->mime = comp->data.mime; + port->service_number = service->number; + } + else if (type == 0x66) + { + sig_component_t *comp; + + if (component_idx == MAX_SIG_COMPONENTS) + { + log_warn("Too many SIG components"); + goto done; + } + + comp = &service->component[component_idx++]; + comp->type = SIG_COMPONENT_AUDIO; + comp->id = p[0]; + comp->audio.port = p[1]; + comp->audio.type = p[2]; + comp->audio.mime = p[7] | (p[8] << 8) | (p[9] << 16) | ((uint32_t)p[10] << 24); + } + p += l - 1; + break; + } + default: + log_warn("unexpected byte %02X", *p); + goto done; + } + } + +done: + nrsc5_report_sig(st->radio, st->services, service_idx); +} + +static aas_port_t *find_port(output_t *st, uint16_t port_id) +{ + unsigned int i; + for (i = 0; i < MAX_PORTS; i++) + { + if (st->ports[i].port == port_id) + return &st->ports[i]; + } + return NULL; +} + +static aas_file_t *find_lot(aas_port_t *port, unsigned int lot) +{ + for (int i = 0; i < MAX_LOT_FILES; i++) + { + if (port->lot.files[i].timestamp == 0) + continue; + if (port->lot.files[i].lot == lot) + return &port->lot.files[i]; + } + return NULL; +} + +static aas_file_t *find_free_lot(aas_port_t *port) +{ + unsigned int min_timestamp = UINT_MAX; + unsigned int min_idx = 0; + aas_file_t *file; + + for (int i = 0; i < MAX_LOT_FILES; i++) + { + unsigned int timestamp = port->lot.files[i].timestamp; + if (timestamp == 0) + return &port->lot.files[i]; + if (timestamp < min_timestamp) + { + min_timestamp = timestamp; + min_idx = i; + } + } + + file = &port->lot.files[min_idx]; + aas_free_lot(file); + return file; +} + +static void process_port(output_t *st, uint16_t port_id, uint8_t *buf, unsigned int len) +{ + static unsigned int counter = 1; + aas_port_t *port; + + if (st->services[0].type == SIG_SERVICE_NONE) + { + // Wait until we receive SIG data. + return; + } + + port = find_port(st, port_id); + if (port == NULL) + { + log_debug("missing port %04X", port_id); + return; + } + + switch (port->type) + { + case AAS_TYPE_STREAM: + { + uint8_t frame_type; + + if (port->stream.data == NULL) + port->stream.data = malloc(MAX_STREAM_BYTES); + + if (port->mime == NRSC5_MIME_HERE_IMAGE) + frame_type = 0xF7; + else + frame_type = 0x0F; + + while (len) + { + uint8_t x = *buf++; + len--; + + // Wait until we find start of a packet. This is either: + // - FF 0F + // - FF F7 FF F7 + if (port->stream.prev[0] == 0xFF && x == frame_type && + (frame_type != 0xF7 || (port->stream.prev[1] == frame_type && port->stream.prev[2] == 0xFF))) + { + if (port->stream.type != 0 && port->stream.idx > 0) + { + port->stream.idx--; + log_debug("Stream data: port=%04X type=%d size=%d size2=%d", port_id, port->stream.type, port->stream.idx, (port->stream.data[0] << 8) | port->stream.data[1]); + } + port->stream.idx = 0; + port->stream.prev[0] = 0; + port->stream.prev[1] = 0; + port->stream.prev[2] = 0; + port->stream.type = x; + } + else + { + if (port->stream.type != 0) + port->stream.data[port->stream.idx++] = x; + port->stream.prev[2] = port->stream.prev[1]; + port->stream.prev[1] = port->stream.prev[0]; + port->stream.prev[0] = x; + } + + if (port->stream.idx == MAX_STREAM_BYTES) + { + log_info("stream packet overflow (%04X)", port_id); + port->stream.type = 0; + } + } + break; + } + case AAS_TYPE_PACKET: + { + if (len < 4) + { + log_warn("bad packet (port %04X, len %d)", port_id, len); + break; + } + log_debug("Packet data: port=%04X size=%d", port_id, len); + break; + } + case AAS_TYPE_LOT: + { + if (len < 8) + { + log_warn("bad fragment (port %04X, len %d)", port_id, len); + return; + } + uint8_t hdrlen = buf[0]; + uint16_t lot = buf[2] | (buf[3] << 8); + uint32_t seq = buf[4] | (buf[5] << 8) | (buf[6] << 16) | ((uint32_t)buf[7] << 24); + if (hdrlen < 8 || hdrlen > len) + { + log_warn("wrong header len (port %04X, len %d, hdrlen %d)", port_id, len, hdrlen); + return; + } + buf += 8; + len -= 8; + hdrlen -= 8; + + if (seq >= MAX_LOT_FRAGMENTS) + { + log_warn("sequence too large (%d)", seq); + return; + } + + aas_file_t *file = find_lot(port, lot); + if (file == NULL) + { + file = find_free_lot(port); + file->lot = lot; + file->fragments = calloc(MAX_LOT_FRAGMENTS, sizeof(uint8_t*)); + } + file->timestamp = counter++; + + if (seq == 0) + { + if (hdrlen < 16) + { + log_warn("header is too short (port %04X, len %d, hdrlen %d)", port_id, len, hdrlen); + return; + } + + // uint32_t == 1 + // uint32_t xxx + // uint32_t size + // uint32_t mimeHash + file->size = buf[8] | (buf[9] << 8) | (buf[10] << 16) | ((uint32_t)buf[11] << 24); + file->mime = buf[12] | (buf[13] << 8) | (buf[14] << 16) | ((uint32_t)buf[15] << 24); + buf += 16; + len -= 16; + hdrlen -= 16; + + // Everything after the fixed header is the filename. + free(file->name); + file->name = strndup((const char *)buf, hdrlen); + buf += hdrlen; + len -= hdrlen; + hdrlen = 0; + + log_debug("File %s, size %d, lot %d, port %04X, mime %08X", file->name, file->size, file->lot, port->port, file->mime); + } + + if (hdrlen != 0) + { + log_warn("unexpected hdrlen (port %04X, hdrlen %d)", port_id, hdrlen); + break; + } + + if (!file->fragments[seq]) + { + uint8_t *fragment = calloc(LOT_FRAGMENT_SIZE, 1); + if (len > LOT_FRAGMENT_SIZE) + { + log_warn("fragment too large (%d)", len); + break; + } + memcpy(fragment, buf, len); + file->fragments[seq] = fragment; + } + + if (file->size) + { + int complete = 1; + int num_fragments = (file->size + LOT_FRAGMENT_SIZE - 1) / LOT_FRAGMENT_SIZE; + for (int i = 0; i < num_fragments; i++) + { + if (file->fragments[i] == NULL) + { + complete = 0; + break; + } + } + if (complete) + { + uint8_t *data = malloc(num_fragments * LOT_FRAGMENT_SIZE); + for (int i = 0; i < num_fragments; i++) + memcpy(data + i * LOT_FRAGMENT_SIZE, file->fragments[i], LOT_FRAGMENT_SIZE); + nrsc5_report_lot(st->radio, port->port, file->lot, file->size, file->mime, file->name, data); + free(data); + aas_free_lot(file); + } + } + break; + } + default: + log_info("unknown port type %d", port->type); + break; + } +} + +void output_aas_push(output_t *st, uint8_t *buf, unsigned int len) +{ + uint16_t port = buf[0] | (buf[1] << 8); + uint16_t seq = buf[2] | (buf[3] << 8); + if (port == 0x5100 || (port >= 0x5201 && port <= 0x5207)) + { + // PSD ports + output_id3(st, port & 0x7, buf + 4, len - 4); + } + else if (port == 0x20) + { + // Station Information Guide + parse_sig(st, buf + 4, len - 4); + } + else if (port >= 0x401 && port <= 0x50FF) + { + process_port(st, port, buf + 4, len - 4); + } + else + { + log_warn("unknown AAS port %04X, seq %04X, length %d", port, seq, len); + } +} diff --git a/src/hddsp/output.h b/src/hddsp/output.h index 231d5d1..ba642cc 100644 --- a/src/hddsp/output.h +++ b/src/hddsp/output.h @@ -1,118 +1,118 @@ -#pragma once - -#include "config.h" - -#include "nrsc5.h" - -#ifdef HAVE_FAAD2 -#include -#endif - -#define AUDIO_FRAME_BYTES 8192 -#define MAX_PORTS 32 -#define MAX_SIG_SERVICES 8 -#define MAX_SIG_COMPONENTS 8 -#define MAX_LOT_FILES 8 -#define LOT_FRAGMENT_SIZE 256 -#define MAX_FILE_BYTES 65536 -#define MAX_LOT_FRAGMENTS (MAX_FILE_BYTES / LOT_FRAGMENT_SIZE) -#define MAX_STREAM_BYTES 65543 - -#define AAS_TYPE_STREAM 0 -#define AAS_TYPE_PACKET 1 -#define AAS_TYPE_LOT 3 - -enum -{ - SIG_COMPONENT_NONE, - SIG_COMPONENT_DATA, - SIG_COMPONENT_AUDIO -}; - -enum -{ - SIG_SERVICE_NONE, - SIG_SERVICE_DATA, - SIG_SERVICE_AUDIO -}; - -typedef struct -{ - unsigned int timestamp; - char *name; - uint32_t mime; - uint16_t lot; - uint32_t size; - uint8_t **fragments; -} aas_file_t; - -typedef struct -{ - uint16_t port; - uint8_t type; - unsigned int service_number; - uint32_t mime; - - union - { - struct - { - uint8_t prev[3]; - uint8_t type; - uint16_t size; - uint8_t *data; - unsigned int idx; - } stream; - struct - { - aas_file_t files[MAX_LOT_FILES]; - } lot; - }; -} aas_port_t; - -typedef struct -{ - uint8_t type; - uint8_t id; - - union - { - struct { - uint16_t port; - uint16_t service_data_type; - uint8_t type; - uint32_t mime; - } data; - struct { - uint8_t port; - uint8_t type; - uint32_t mime; - } audio; - }; -} sig_component_t; - -typedef struct -{ - uint8_t type; - uint16_t number; - char *name; - - sig_component_t component[MAX_SIG_COMPONENTS]; -} sig_service_t; - -typedef struct -{ - nrsc5_t *radio; -#ifdef HAVE_FAAD2 - NeAACDecHandle aacdec[MAX_PROGRAMS]; -#endif - aas_port_t ports[MAX_PORTS]; - sig_service_t services[MAX_SIG_SERVICES]; -} output_t; - -void output_push(output_t *st, uint8_t *pkt, unsigned int len, unsigned int program, unsigned int stream_id); -void output_begin(output_t *st); -void output_reset(output_t *st); -void output_init(output_t *st, nrsc5_t *); -void output_free(output_t *st); -void output_aas_push(output_t *st, uint8_t *psd, unsigned int len); +#pragma once + +#include "config.h" + +#include "nrsc5.h" + +#ifdef HAVE_FAAD2 +#include +#endif + +#define AUDIO_FRAME_BYTES 8192 +#define MAX_PORTS 32 +#define MAX_SIG_SERVICES 8 +#define MAX_SIG_COMPONENTS 8 +#define MAX_LOT_FILES 8 +#define LOT_FRAGMENT_SIZE 256 +#define MAX_FILE_BYTES 65536 +#define MAX_LOT_FRAGMENTS (MAX_FILE_BYTES / LOT_FRAGMENT_SIZE) +#define MAX_STREAM_BYTES 65543 + +#define AAS_TYPE_STREAM 0 +#define AAS_TYPE_PACKET 1 +#define AAS_TYPE_LOT 3 + +enum +{ + SIG_COMPONENT_NONE, + SIG_COMPONENT_DATA, + SIG_COMPONENT_AUDIO +}; + +enum +{ + SIG_SERVICE_NONE, + SIG_SERVICE_DATA, + SIG_SERVICE_AUDIO +}; + +typedef struct +{ + unsigned int timestamp; + char *name; + uint32_t mime; + uint16_t lot; + uint32_t size; + uint8_t **fragments; +} aas_file_t; + +typedef struct +{ + uint16_t port; + uint8_t type; + unsigned int service_number; + uint32_t mime; + + union + { + struct + { + uint8_t prev[3]; + uint8_t type; + uint16_t size; + uint8_t *data; + unsigned int idx; + } stream; + struct + { + aas_file_t files[MAX_LOT_FILES]; + } lot; + }; +} aas_port_t; + +typedef struct +{ + uint8_t type; + uint8_t id; + + union + { + struct { + uint16_t port; + uint16_t service_data_type; + uint8_t type; + uint32_t mime; + } data; + struct { + uint8_t port; + uint8_t type; + uint32_t mime; + } audio; + }; +} sig_component_t; + +typedef struct +{ + uint8_t type; + uint16_t number; + char *name; + + sig_component_t component[MAX_SIG_COMPONENTS]; +} sig_service_t; + +typedef struct +{ + nrsc5_t *radio; +#ifdef HAVE_FAAD2 + NeAACDecHandle aacdec[MAX_PROGRAMS]; +#endif + aas_port_t ports[MAX_PORTS]; + sig_service_t services[MAX_SIG_SERVICES]; +} output_t; + +void output_push(output_t *st, uint8_t *pkt, unsigned int len, unsigned int program, unsigned int stream_id); +void output_begin(output_t *st); +void output_reset(output_t *st); +void output_init(output_t *st, nrsc5_t *); +void output_free(output_t *st); +void output_aas_push(output_t *st, uint8_t *psd, unsigned int len); diff --git a/src/hddsp/pids.c b/src/hddsp/pids.c index c828fac..2c0ae06 100644 --- a/src/hddsp/pids.c +++ b/src/hddsp/pids.c @@ -1,621 +1,621 @@ -/* - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include - -#include "defines.h" -#include "pids.h" -#include "private.h" -#include "unicode.h" - -static char *chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ ?-*$ "; - -static uint16_t crc12(uint8_t *bits) -{ - uint16_t poly = 0xD010; - uint16_t reg = 0x0000; - int i, lowbit; - - for (i = 67; i >= 0; i--) - { - lowbit = reg & 1; - reg >>= 1; - reg ^= ((uint16_t)bits[i] << 15); - if (lowbit) reg ^= poly; - } - for (i = 0; i < 16; i++) - { - lowbit = reg & 1; - reg >>= 1; - if (lowbit) reg ^= poly; - } - reg ^= 0x955; - return reg & 0xfff; -} - -static int check_crc12(uint8_t *bits) -{ - uint16_t expected_crc = 0; - int i; - - for (i = 68; i < 80; i++) - { - expected_crc <<= 1; - expected_crc |= bits[i]; - } - return expected_crc == crc12(bits); -} - -static unsigned int decode_int(uint8_t *bits, int *off, unsigned int length) -{ - unsigned int i, result = 0; - for (i = 0; i < length; i++) - { - result <<= 1; - result |= bits[(*off)++]; - } - return result; -} - -static int decode_signed_int(uint8_t *bits, int *off, unsigned int length) -{ - int result = (int) decode_int(bits, off, length); - return (result & (1 << (length - 1))) ? result - (1 << length) : result; -} - -static char decode_char5(uint8_t *bits, int *off) -{ - return chars[decode_int(bits, off, 5)]; -} - -static char decode_char7(uint8_t *bits, int *off) -{ - return (char) decode_int(bits, off, 7); -} - -static char *utf8_encode(int encoding, char *buf, int len) -{ - if (encoding == 0) - return iso_8859_1_to_utf_8((uint8_t *) buf, len); - else if (encoding == 4) - return ucs_2_to_utf_8((uint8_t *) buf, len); - else - log_warn("Invalid encoding: %d", encoding); - - return NULL; -} - -static void report(pids_t *st) -{ - int i; - const char *country_code = NULL; - const char *name = NULL; - char *slogan = NULL; - char *message = NULL; - char *alert = NULL; - float latitude = NAN; - float longitude = NAN; - int altitude = 0; - nrsc5_sis_asd_t *audio_services = NULL; - nrsc5_sis_dsd_t *data_services = NULL; - - if (st->country_code[0] != 0) - country_code = st->country_code; - - if (st->short_name[0] != 0) - name = st->short_name; - - if (st->slogan_displayed) - slogan = utf8_encode(st->slogan_encoding, st->slogan, st->slogan_len); - else if (st->long_name_displayed) - slogan = strdup(st->long_name); - - if (st->message_displayed) - message = utf8_encode(st->message_encoding, st->message, st->message_len); - - if (st->alert_displayed) - { - int cnt_bytes = 1 + (2 * st->alert_cnt_len); - alert = utf8_encode(st->alert_encoding, st->alert + cnt_bytes, st->alert_len - cnt_bytes); - } - - if (!isnan(st->latitude) && !isnan(st->longitude)) - { - latitude = st->latitude; - longitude = st->longitude; - altitude = st->altitude; - } - - for (i = MAX_AUDIO_SERVICES - 1; i >= 0; i--) - { - if (st->audio_services[i].type != -1) - { - nrsc5_sis_asd_t *asd = malloc(sizeof(nrsc5_sis_asd_t)); - asd->next = audio_services; - asd->program = i; - asd->access = st->audio_services[i].access; - asd->type = st->audio_services[i].type; - asd->sound_exp = st->audio_services[i].sound_exp; - audio_services = asd; - } - } - - for (i = MAX_DATA_SERVICES - 1; i >= 0; i--) - { - if (st->data_services[i].type != -1) - { - nrsc5_sis_dsd_t *dsd = malloc(sizeof(nrsc5_sis_dsd_t)); - dsd->next = data_services; - dsd->access = st->data_services[i].access; - dsd->type = st->data_services[i].type; - dsd->mime_type = st->data_services[i].mime_type; - data_services = dsd; - } - } - - nrsc5_report_sis(st->input->radio, country_code, st->fcc_facility_id, name, slogan, message, alert, - latitude, longitude, altitude, audio_services, data_services); - - free(slogan); - free(message); - free(alert); - - while (audio_services) - { - nrsc5_sis_asd_t *asd = audio_services; - audio_services = asd->next; - free(asd); - } - - while (data_services) - { - nrsc5_sis_dsd_t *dsd = data_services; - data_services = dsd->next; - free(dsd); - } -} - -static void decode_sis(pids_t *st, uint8_t *bits) -{ - int payloads, off, i; - int updated = 0; - - if (bits[0] != 0) return; - payloads = bits[1] + 1; - off = 2; - - for (i = 0; i < payloads; i++) - { - int msg_id, j; - char country_code[3] = {0}; - int fcc_facility_id; - char short_name[8] = {0}; - int seq; - int current_frame; - int last_frame; - float latitude, longitude; - int category, prog_num; - asd_t audio_service; - dsd_t data_service; - int index, parameter, tzo; - - if (off > 60) break; - msg_id = decode_int(bits, &off, 4); - - switch (msg_id) - { - case 0: - if (off > 64 - 32) break; - for (j = 0; j < 2; j++) - { - country_code[j] = decode_char5(bits, &off); - } - off += 3; // reserved - fcc_facility_id = decode_int(bits, &off, 19); - - if ((strcmp(country_code, st->country_code) != 0) || (fcc_facility_id != st->fcc_facility_id)) - { - strcpy(st->country_code, country_code); - st->fcc_facility_id = fcc_facility_id; - updated = 1; - } - break; - case 1: - if (off > 64 - 22) break; - for (j = 0; j < 4; j++) - { - short_name[j] = decode_char5(bits, &off); - } - if (bits[off] == 0 && bits[off+1] == 1) - strcat(short_name, "-FM"); - off += 2; - - if (strcmp(short_name, st->short_name) != 0) - { - strcpy(st->short_name, short_name); - updated = 1; - } - break; - case 2: - if (off > 64 - 58) break; - off += 55; - seq = decode_int(bits, &off, 3); - off -= 58; - - last_frame = decode_int(bits, &off, 3); - current_frame = decode_int(bits, &off, 3); - - if ((current_frame == 0) && (seq != st->long_name_seq)) - { - memset(st->long_name, 0, sizeof(st->long_name)); - memset(st->long_name_have_frame, 0, sizeof(st->long_name_have_frame)); - st->long_name_seq = seq; - st->long_name_displayed = 0; - } - - for (j = 0; j < 7; j++) - st->long_name[current_frame * 7 + j] = decode_char7(bits, &off); - st->long_name_have_frame[current_frame] = 1; - - if ((st->long_name_seq >= 0) && !st->long_name_displayed) - { - int complete = 1; - for (j = 0; j < last_frame + 1; j++) - complete &= st->long_name_have_frame[j]; - - if (complete) - { - st->long_name_displayed = 1; - updated = 1; - } - } - - off += 3; - break; - case 3: - off += 32; - break; - case 4: - if (off > 64 - 27) break; - if (bits[off++]) - { - latitude = decode_signed_int(bits, &off, 22) / 8192.0; - st->altitude = (st->altitude & 0x0f0) | (decode_int(bits, &off, 4) << 8); - if (latitude != st->latitude) - { - st->latitude = latitude; - if (!isnan(st->longitude)) - updated = 1; - } - } - else - { - longitude = decode_signed_int(bits, &off, 22) / 8192.0; - st->altitude = (st->altitude & 0xf00) | (decode_int(bits, &off, 4) << 4); - if (longitude != st->longitude) - { - st->longitude = longitude; - if (!isnan(st->latitude)) - updated = 1; - } - } - break; - case 5: - if (off > 64 - 58) break; - current_frame = decode_int(bits, &off, 5); - seq = decode_int(bits, &off, 2); - - if (current_frame == 0) - { - if (seq != st->message_seq) - { - memset(st->message, 0, sizeof(st->message)); - memset(st->message_have_frame, 0, sizeof(st->message_have_frame)); - st->message_seq = seq; - st->message_displayed = 0; - } - st->message_priority = bits[off++]; - st->message_encoding = decode_int(bits, &off, 3); - st->message_len = decode_int(bits, &off, 8); - off += 7; // checksum - for (j = 0; j < 4; j++) - st->message[j] = decode_int(bits, &off, 8); - } - else - { - off += 3; // reserved - for (j = 0; j < 6; j++) - st->message[current_frame * 6 - 2 + j] = decode_int(bits, &off, 8); - } - st->message_have_frame[current_frame] = 1; - - if ((st->message_seq >= 0) && !st->message_displayed) - { - int complete = 1; - for (j = 0; j < (st->message_len + 7) / 6; j++) - complete &= st->message_have_frame[j]; - - if (complete) - { - st->message_displayed = 1; - updated = 1; - } - } - break; - case 6: - if (off > 64 - 27) break; - category = decode_int(bits, &off, 2); - switch (category) - { - case 0: - audio_service.access = decode_int(bits, &off, 1); - prog_num = decode_int(bits, &off, 6); - audio_service.type = decode_int(bits, &off, 8); - off += 5; // reserved - audio_service.sound_exp = decode_int(bits, &off, 5); - - if (prog_num >= MAX_AUDIO_SERVICES) - { - log_warn("Invalid program number: %d", prog_num); - break; - } - - if (st->audio_services[prog_num].access != audio_service.access - || st->audio_services[prog_num].type != audio_service.type - || st->audio_services[prog_num].sound_exp != audio_service.sound_exp) - { - st->audio_services[prog_num] = audio_service; - updated = 1; - } - break; - case 1: - data_service.access = decode_int(bits, &off, 1); - data_service.type = decode_int(bits, &off, 9); - off += 3; // reserved - data_service.mime_type = decode_int(bits, &off, 12); - - for (j = 0; j < MAX_DATA_SERVICES; j++) - { - if (st->data_services[j].access == data_service.access - && st->data_services[j].type == data_service.type - && st->data_services[j].mime_type == data_service.mime_type) - { - break; - } - else if (st->data_services[j].type == -1) - { - st->data_services[j] = data_service; - updated = 1; - break; - } - } - break; - default: - log_warn("Unknown service category identifier: %d", category); - } - break; - case 7: - if (off > 64 - 22) break; - index = decode_int(bits, &off, 6); - parameter = decode_int(bits, &off, 16); - if (index >= NUM_PARAMETERS) - { - log_warn("Invalid parameter index: %d", index); - break; - } - if (st->parameters[index] != parameter) - { - st->parameters[index] = parameter; - switch (index) - { - case 0: - log_debug("Pending leap second offset: %d, current leap second offset: %d", - parameter >> 8, parameter & 0xff); - break; - case 1: - case 2: - if (st->parameters[1] >= 0 && st->parameters[2] >= 0) - log_debug("ALFN of pending leap second adjustment: %d", st->parameters[2] << 16 | st->parameters[1]); - break; - case 3: - tzo = (parameter >> 5) & 0x7ff; - if (tzo > 1024) tzo -= 2048; - log_debug("Local time zone offset: %d minutes, DST sched. %d, local DST? %s, regional DST? %s", - tzo, (parameter >> 2) & 0x7, parameter & 0x2 ? "yes" : "no", parameter & 0x1 ? "yes" : "no"); - break; - case 4: - case 5: - case 6: - case 7: - if (st->parameters[4] >= 0 && st->parameters[5] >= 0 && st->parameters[6] >= 0 && st->parameters[7] >= 0) - { - log_debug("Exciter manuf. \"%c%c\", core version %d.%d.%d.%d-%d, manuf. version %d.%d.%d.%d-%d", - (st->parameters[4] >> 8) & 0x7f, st->parameters[4] & 0x7f, - (st->parameters[5] >> 11) & 0x1f, (st->parameters[5] >> 6) & 0x1f, (st->parameters[5] >> 1) & 0x1f, - (st->parameters[7] >> 11) & 0x1f, (st->parameters[7] >> 3) & 0x7, - (st->parameters[6] >> 11) & 0x1f, (st->parameters[6] >> 6) & 0x1f, (st->parameters[6] >> 1) & 0x1f, - (st->parameters[7] >> 6) & 0x1f, st->parameters[7] & 0x7 - ); - } - break; - case 8: - case 9: - case 10: - case 11: - if (st->parameters[8] >= 0 && st->parameters[9] >= 0 && st->parameters[10] >= 0 && st->parameters[11] >= 0) - { - log_debug("Importer manuf. \"%c%c\", core version %d.%d.%d.%d-%d, manuf. version %d.%d.%d.%d-%d", - (st->parameters[8] >> 8) & 0x7f, st->parameters[8] & 0x7f, - (st->parameters[9] >> 11) & 0x1f, (st->parameters[9] >> 6) & 0x1f, (st->parameters[9] >> 1) & 0x1f, - (st->parameters[11] >> 11) & 0x1f, (st->parameters[11] >> 3) & 0x7, - (st->parameters[10] >> 11) & 0x1f, (st->parameters[10] >> 6) & 0x1f, (st->parameters[10] >> 1) & 0x1f, - (st->parameters[11] >> 6) & 0x1f, st->parameters[11] & 0x7 - ); - } - break; - } - } - break; - case 8: - if (off > 64 - 58) break; - current_frame = decode_int(bits, &off, 4); - if (bits[off++] == 0) - { - // Fixme: implement Universal Short Station Name - off += 53; - } - else - { - if (current_frame == 0) - { - st->slogan_encoding = decode_int(bits, &off, 3); - off += 3; // reserved - st->slogan_len = decode_int(bits, &off, 7); - for (j = 0; j < 5; j++) - st->slogan[j] = decode_int(bits, &off, 8); - } - else - { - off += 5; // reserved - for (j = 0; j < 6; j++) - st->slogan[current_frame * 6 - 1 + j] = decode_int(bits, &off, 8); - } - st->slogan_have_frame[current_frame] = 1; - - if (st->slogan_len >= 0 && !st->slogan_displayed) - { - int complete = 1; - for (j = 0; j < (st->slogan_len + 6) / 6; j++) - complete &= st->slogan_have_frame[j]; - - if (complete) - { - st->slogan_displayed = 1; - updated = 1; - } - } - } - break; - case 9: - if (off > 64 - 58) break; - current_frame = decode_int(bits, &off, 6); - seq = decode_int(bits, &off, 2); - off += 2; // reserved - - if (current_frame == 0) - { - if (seq != st->alert_seq) - { - memset(st->alert, 0, sizeof(st->alert)); - memset(st->alert_have_frame, 0, sizeof(st->alert_have_frame)); - st->alert_seq = seq; - st->alert_displayed = 0; - } - st->alert_encoding = decode_int(bits, &off, 3); - st->alert_len = decode_int(bits, &off, 9); - off += 7; // CRC-7 integrity check - st->alert_cnt_len = decode_int(bits, &off, 5); - for (j = 0; j < 3; j++) - st->alert[j] = decode_int(bits, &off, 8); - } - else - { - for (j = 0; j < 6; j++) - st->alert[current_frame * 6 - 3 + j] = decode_int(bits, &off, 8); - } - st->alert_have_frame[current_frame] = 1; - - if (st->alert_len >= 0 && !st->alert_displayed) - { - int complete = 1; - for (j = 0; j < (st->alert_len + 8) / 6; j++) - complete &= st->alert_have_frame[j]; - - if (complete) - { - st->alert_displayed = 1; - updated = 1; - } - } - break; - default: - log_error("unexpected msg_id: %d", msg_id); - } - } - - if (updated == 1) - report(st); -} - -void pids_frame_push(pids_t *st, uint8_t *bits) -{ - int i; - uint8_t reversed[PIDS_FRAME_LEN]; - - for (i = 0; i < PIDS_FRAME_LEN; i++) - { - reversed[i] = bits[((i>>3)<<3) + 7 - (i & 7)]; - } - if (check_crc12(reversed)) - decode_sis(st, reversed); -} - -void pids_init(pids_t *st, input_t *input) -{ - int i; - - memset(st->country_code, 0, sizeof(st->country_code)); - st->fcc_facility_id = 0; - - memset(st->short_name, 0, sizeof(st->short_name)); - - st->long_name_seq = -1; - st->long_name_displayed = 0; - - st->latitude = NAN; - st->longitude = NAN; - st->altitude = 0; - - st->message_seq = -1; - st->message_displayed = 0; - - for (i = 0; i < MAX_AUDIO_SERVICES; i++) - { - st->audio_services[i].access = -1; - st->audio_services[i].type = -1; - st->audio_services[i].sound_exp = -1; - } - for (i = 0; i < MAX_DATA_SERVICES; i++) - { - st->data_services[i].access = -1; - st->data_services[i].type = -1; - st->data_services[i].mime_type = -1; - } - - for (i = 0; i < NUM_PARAMETERS; i++) - st->parameters[i] = -1; - - memset(st->slogan, 0, sizeof(st->slogan)); - memset(st->slogan_have_frame, 0, sizeof(st->slogan_have_frame)); - st->slogan_len = -1; - st->slogan_displayed = 0; - - memset(st->alert, 0, sizeof(st->alert)); - memset(st->alert_have_frame, 0, sizeof(st->alert_have_frame)); - st->alert_seq = -1; - st->alert_displayed = 0; - - st->input = input; -} +/* + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include "defines.h" +#include "pids.h" +#include "private.h" +#include "unicode.h" + +static char *chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ ?-*$ "; + +static uint16_t crc12(uint8_t *bits) +{ + uint16_t poly = 0xD010; + uint16_t reg = 0x0000; + int i, lowbit; + + for (i = 67; i >= 0; i--) + { + lowbit = reg & 1; + reg >>= 1; + reg ^= ((uint16_t)bits[i] << 15); + if (lowbit) reg ^= poly; + } + for (i = 0; i < 16; i++) + { + lowbit = reg & 1; + reg >>= 1; + if (lowbit) reg ^= poly; + } + reg ^= 0x955; + return reg & 0xfff; +} + +static int check_crc12(uint8_t *bits) +{ + uint16_t expected_crc = 0; + int i; + + for (i = 68; i < 80; i++) + { + expected_crc <<= 1; + expected_crc |= bits[i]; + } + return expected_crc == crc12(bits); +} + +static unsigned int decode_int(uint8_t *bits, int *off, unsigned int length) +{ + unsigned int i, result = 0; + for (i = 0; i < length; i++) + { + result <<= 1; + result |= bits[(*off)++]; + } + return result; +} + +static int decode_signed_int(uint8_t *bits, int *off, unsigned int length) +{ + int result = (int) decode_int(bits, off, length); + return (result & (1 << (length - 1))) ? result - (1 << length) : result; +} + +static char decode_char5(uint8_t *bits, int *off) +{ + return chars[decode_int(bits, off, 5)]; +} + +static char decode_char7(uint8_t *bits, int *off) +{ + return (char) decode_int(bits, off, 7); +} + +static char *utf8_encode(int encoding, char *buf, int len) +{ + if (encoding == 0) + return iso_8859_1_to_utf_8((uint8_t *) buf, len); + else if (encoding == 4) + return ucs_2_to_utf_8((uint8_t *) buf, len); + else + log_warn("Invalid encoding: %d", encoding); + + return NULL; +} + +static void report(pids_t *st) +{ + int i; + const char *country_code = NULL; + const char *name = NULL; + char *slogan = NULL; + char *message = NULL; + char *alert = NULL; + float latitude = NAN; + float longitude = NAN; + int altitude = 0; + nrsc5_sis_asd_t *audio_services = NULL; + nrsc5_sis_dsd_t *data_services = NULL; + + if (st->country_code[0] != 0) + country_code = st->country_code; + + if (st->short_name[0] != 0) + name = st->short_name; + + if (st->slogan_displayed) + slogan = utf8_encode(st->slogan_encoding, st->slogan, st->slogan_len); + else if (st->long_name_displayed) + slogan = strdup(st->long_name); + + if (st->message_displayed) + message = utf8_encode(st->message_encoding, st->message, st->message_len); + + if (st->alert_displayed) + { + int cnt_bytes = 1 + (2 * st->alert_cnt_len); + alert = utf8_encode(st->alert_encoding, st->alert + cnt_bytes, st->alert_len - cnt_bytes); + } + + if (!isnan(st->latitude) && !isnan(st->longitude)) + { + latitude = st->latitude; + longitude = st->longitude; + altitude = st->altitude; + } + + for (i = MAX_AUDIO_SERVICES - 1; i >= 0; i--) + { + if (st->audio_services[i].type != -1) + { + nrsc5_sis_asd_t *asd = malloc(sizeof(nrsc5_sis_asd_t)); + asd->next = audio_services; + asd->program = i; + asd->access = st->audio_services[i].access; + asd->type = st->audio_services[i].type; + asd->sound_exp = st->audio_services[i].sound_exp; + audio_services = asd; + } + } + + for (i = MAX_DATA_SERVICES - 1; i >= 0; i--) + { + if (st->data_services[i].type != -1) + { + nrsc5_sis_dsd_t *dsd = malloc(sizeof(nrsc5_sis_dsd_t)); + dsd->next = data_services; + dsd->access = st->data_services[i].access; + dsd->type = st->data_services[i].type; + dsd->mime_type = st->data_services[i].mime_type; + data_services = dsd; + } + } + + nrsc5_report_sis(st->input->radio, country_code, st->fcc_facility_id, name, slogan, message, alert, + latitude, longitude, altitude, audio_services, data_services); + + free(slogan); + free(message); + free(alert); + + while (audio_services) + { + nrsc5_sis_asd_t *asd = audio_services; + audio_services = asd->next; + free(asd); + } + + while (data_services) + { + nrsc5_sis_dsd_t *dsd = data_services; + data_services = dsd->next; + free(dsd); + } +} + +static void decode_sis(pids_t *st, uint8_t *bits) +{ + int payloads, off, i; + int updated = 0; + + if (bits[0] != 0) return; + payloads = bits[1] + 1; + off = 2; + + for (i = 0; i < payloads; i++) + { + int msg_id, j; + char country_code[3] = {0}; + int fcc_facility_id; + char short_name[8] = {0}; + int seq; + int current_frame; + int last_frame; + float latitude, longitude; + int category, prog_num; + asd_t audio_service; + dsd_t data_service; + int index, parameter, tzo; + + if (off > 60) break; + msg_id = decode_int(bits, &off, 4); + + switch (msg_id) + { + case 0: + if (off > 64 - 32) break; + for (j = 0; j < 2; j++) + { + country_code[j] = decode_char5(bits, &off); + } + off += 3; // reserved + fcc_facility_id = decode_int(bits, &off, 19); + + if ((strcmp(country_code, st->country_code) != 0) || (fcc_facility_id != st->fcc_facility_id)) + { + strcpy(st->country_code, country_code); + st->fcc_facility_id = fcc_facility_id; + updated = 1; + } + break; + case 1: + if (off > 64 - 22) break; + for (j = 0; j < 4; j++) + { + short_name[j] = decode_char5(bits, &off); + } + if (bits[off] == 0 && bits[off+1] == 1) + strcat(short_name, "-FM"); + off += 2; + + if (strcmp(short_name, st->short_name) != 0) + { + strcpy(st->short_name, short_name); + updated = 1; + } + break; + case 2: + if (off > 64 - 58) break; + off += 55; + seq = decode_int(bits, &off, 3); + off -= 58; + + last_frame = decode_int(bits, &off, 3); + current_frame = decode_int(bits, &off, 3); + + if ((current_frame == 0) && (seq != st->long_name_seq)) + { + memset(st->long_name, 0, sizeof(st->long_name)); + memset(st->long_name_have_frame, 0, sizeof(st->long_name_have_frame)); + st->long_name_seq = seq; + st->long_name_displayed = 0; + } + + for (j = 0; j < 7; j++) + st->long_name[current_frame * 7 + j] = decode_char7(bits, &off); + st->long_name_have_frame[current_frame] = 1; + + if ((st->long_name_seq >= 0) && !st->long_name_displayed) + { + int complete = 1; + for (j = 0; j < last_frame + 1; j++) + complete &= st->long_name_have_frame[j]; + + if (complete) + { + st->long_name_displayed = 1; + updated = 1; + } + } + + off += 3; + break; + case 3: + off += 32; + break; + case 4: + if (off > 64 - 27) break; + if (bits[off++]) + { + latitude = decode_signed_int(bits, &off, 22) / 8192.0; + st->altitude = (st->altitude & 0x0f0) | (decode_int(bits, &off, 4) << 8); + if (latitude != st->latitude) + { + st->latitude = latitude; + if (!isnan(st->longitude)) + updated = 1; + } + } + else + { + longitude = decode_signed_int(bits, &off, 22) / 8192.0; + st->altitude = (st->altitude & 0xf00) | (decode_int(bits, &off, 4) << 4); + if (longitude != st->longitude) + { + st->longitude = longitude; + if (!isnan(st->latitude)) + updated = 1; + } + } + break; + case 5: + if (off > 64 - 58) break; + current_frame = decode_int(bits, &off, 5); + seq = decode_int(bits, &off, 2); + + if (current_frame == 0) + { + if (seq != st->message_seq) + { + memset(st->message, 0, sizeof(st->message)); + memset(st->message_have_frame, 0, sizeof(st->message_have_frame)); + st->message_seq = seq; + st->message_displayed = 0; + } + st->message_priority = bits[off++]; + st->message_encoding = decode_int(bits, &off, 3); + st->message_len = decode_int(bits, &off, 8); + off += 7; // checksum + for (j = 0; j < 4; j++) + st->message[j] = decode_int(bits, &off, 8); + } + else + { + off += 3; // reserved + for (j = 0; j < 6; j++) + st->message[current_frame * 6 - 2 + j] = decode_int(bits, &off, 8); + } + st->message_have_frame[current_frame] = 1; + + if ((st->message_seq >= 0) && !st->message_displayed) + { + int complete = 1; + for (j = 0; j < (st->message_len + 7) / 6; j++) + complete &= st->message_have_frame[j]; + + if (complete) + { + st->message_displayed = 1; + updated = 1; + } + } + break; + case 6: + if (off > 64 - 27) break; + category = decode_int(bits, &off, 2); + switch (category) + { + case 0: + audio_service.access = decode_int(bits, &off, 1); + prog_num = decode_int(bits, &off, 6); + audio_service.type = decode_int(bits, &off, 8); + off += 5; // reserved + audio_service.sound_exp = decode_int(bits, &off, 5); + + if (prog_num >= MAX_AUDIO_SERVICES) + { + log_warn("Invalid program number: %d", prog_num); + break; + } + + if (st->audio_services[prog_num].access != audio_service.access + || st->audio_services[prog_num].type != audio_service.type + || st->audio_services[prog_num].sound_exp != audio_service.sound_exp) + { + st->audio_services[prog_num] = audio_service; + updated = 1; + } + break; + case 1: + data_service.access = decode_int(bits, &off, 1); + data_service.type = decode_int(bits, &off, 9); + off += 3; // reserved + data_service.mime_type = decode_int(bits, &off, 12); + + for (j = 0; j < MAX_DATA_SERVICES; j++) + { + if (st->data_services[j].access == data_service.access + && st->data_services[j].type == data_service.type + && st->data_services[j].mime_type == data_service.mime_type) + { + break; + } + else if (st->data_services[j].type == -1) + { + st->data_services[j] = data_service; + updated = 1; + break; + } + } + break; + default: + log_warn("Unknown service category identifier: %d", category); + } + break; + case 7: + if (off > 64 - 22) break; + index = decode_int(bits, &off, 6); + parameter = decode_int(bits, &off, 16); + if (index >= NUM_PARAMETERS) + { + log_warn("Invalid parameter index: %d", index); + break; + } + if (st->parameters[index] != parameter) + { + st->parameters[index] = parameter; + switch (index) + { + case 0: + log_debug("Pending leap second offset: %d, current leap second offset: %d", + parameter >> 8, parameter & 0xff); + break; + case 1: + case 2: + if (st->parameters[1] >= 0 && st->parameters[2] >= 0) + log_debug("ALFN of pending leap second adjustment: %d", st->parameters[2] << 16 | st->parameters[1]); + break; + case 3: + tzo = (parameter >> 5) & 0x7ff; + if (tzo > 1024) tzo -= 2048; + log_debug("Local time zone offset: %d minutes, DST sched. %d, local DST? %s, regional DST? %s", + tzo, (parameter >> 2) & 0x7, parameter & 0x2 ? "yes" : "no", parameter & 0x1 ? "yes" : "no"); + break; + case 4: + case 5: + case 6: + case 7: + if (st->parameters[4] >= 0 && st->parameters[5] >= 0 && st->parameters[6] >= 0 && st->parameters[7] >= 0) + { + log_debug("Exciter manuf. \"%c%c\", core version %d.%d.%d.%d-%d, manuf. version %d.%d.%d.%d-%d", + (st->parameters[4] >> 8) & 0x7f, st->parameters[4] & 0x7f, + (st->parameters[5] >> 11) & 0x1f, (st->parameters[5] >> 6) & 0x1f, (st->parameters[5] >> 1) & 0x1f, + (st->parameters[7] >> 11) & 0x1f, (st->parameters[7] >> 3) & 0x7, + (st->parameters[6] >> 11) & 0x1f, (st->parameters[6] >> 6) & 0x1f, (st->parameters[6] >> 1) & 0x1f, + (st->parameters[7] >> 6) & 0x1f, st->parameters[7] & 0x7 + ); + } + break; + case 8: + case 9: + case 10: + case 11: + if (st->parameters[8] >= 0 && st->parameters[9] >= 0 && st->parameters[10] >= 0 && st->parameters[11] >= 0) + { + log_debug("Importer manuf. \"%c%c\", core version %d.%d.%d.%d-%d, manuf. version %d.%d.%d.%d-%d", + (st->parameters[8] >> 8) & 0x7f, st->parameters[8] & 0x7f, + (st->parameters[9] >> 11) & 0x1f, (st->parameters[9] >> 6) & 0x1f, (st->parameters[9] >> 1) & 0x1f, + (st->parameters[11] >> 11) & 0x1f, (st->parameters[11] >> 3) & 0x7, + (st->parameters[10] >> 11) & 0x1f, (st->parameters[10] >> 6) & 0x1f, (st->parameters[10] >> 1) & 0x1f, + (st->parameters[11] >> 6) & 0x1f, st->parameters[11] & 0x7 + ); + } + break; + } + } + break; + case 8: + if (off > 64 - 58) break; + current_frame = decode_int(bits, &off, 4); + if (bits[off++] == 0) + { + // Fixme: implement Universal Short Station Name + off += 53; + } + else + { + if (current_frame == 0) + { + st->slogan_encoding = decode_int(bits, &off, 3); + off += 3; // reserved + st->slogan_len = decode_int(bits, &off, 7); + for (j = 0; j < 5; j++) + st->slogan[j] = decode_int(bits, &off, 8); + } + else + { + off += 5; // reserved + for (j = 0; j < 6; j++) + st->slogan[current_frame * 6 - 1 + j] = decode_int(bits, &off, 8); + } + st->slogan_have_frame[current_frame] = 1; + + if (st->slogan_len >= 0 && !st->slogan_displayed) + { + int complete = 1; + for (j = 0; j < (st->slogan_len + 6) / 6; j++) + complete &= st->slogan_have_frame[j]; + + if (complete) + { + st->slogan_displayed = 1; + updated = 1; + } + } + } + break; + case 9: + if (off > 64 - 58) break; + current_frame = decode_int(bits, &off, 6); + seq = decode_int(bits, &off, 2); + off += 2; // reserved + + if (current_frame == 0) + { + if (seq != st->alert_seq) + { + memset(st->alert, 0, sizeof(st->alert)); + memset(st->alert_have_frame, 0, sizeof(st->alert_have_frame)); + st->alert_seq = seq; + st->alert_displayed = 0; + } + st->alert_encoding = decode_int(bits, &off, 3); + st->alert_len = decode_int(bits, &off, 9); + off += 7; // CRC-7 integrity check + st->alert_cnt_len = decode_int(bits, &off, 5); + for (j = 0; j < 3; j++) + st->alert[j] = decode_int(bits, &off, 8); + } + else + { + for (j = 0; j < 6; j++) + st->alert[current_frame * 6 - 3 + j] = decode_int(bits, &off, 8); + } + st->alert_have_frame[current_frame] = 1; + + if (st->alert_len >= 0 && !st->alert_displayed) + { + int complete = 1; + for (j = 0; j < (st->alert_len + 8) / 6; j++) + complete &= st->alert_have_frame[j]; + + if (complete) + { + st->alert_displayed = 1; + updated = 1; + } + } + break; + default: + log_error("unexpected msg_id: %d", msg_id); + } + } + + if (updated == 1) + report(st); +} + +void pids_frame_push(pids_t *st, uint8_t *bits) +{ + int i; + uint8_t reversed[PIDS_FRAME_LEN]; + + for (i = 0; i < PIDS_FRAME_LEN; i++) + { + reversed[i] = bits[((i>>3)<<3) + 7 - (i & 7)]; + } + if (check_crc12(reversed)) + decode_sis(st, reversed); +} + +void pids_init(pids_t *st, input_t *input) +{ + int i; + + memset(st->country_code, 0, sizeof(st->country_code)); + st->fcc_facility_id = 0; + + memset(st->short_name, 0, sizeof(st->short_name)); + + st->long_name_seq = -1; + st->long_name_displayed = 0; + + st->latitude = NAN; + st->longitude = NAN; + st->altitude = 0; + + st->message_seq = -1; + st->message_displayed = 0; + + for (i = 0; i < MAX_AUDIO_SERVICES; i++) + { + st->audio_services[i].access = -1; + st->audio_services[i].type = -1; + st->audio_services[i].sound_exp = -1; + } + for (i = 0; i < MAX_DATA_SERVICES; i++) + { + st->data_services[i].access = -1; + st->data_services[i].type = -1; + st->data_services[i].mime_type = -1; + } + + for (i = 0; i < NUM_PARAMETERS; i++) + st->parameters[i] = -1; + + memset(st->slogan, 0, sizeof(st->slogan)); + memset(st->slogan_have_frame, 0, sizeof(st->slogan_have_frame)); + st->slogan_len = -1; + st->slogan_displayed = 0; + + memset(st->alert, 0, sizeof(st->alert)); + memset(st->alert_have_frame, 0, sizeof(st->alert_have_frame)); + st->alert_seq = -1; + st->alert_displayed = 0; + + st->input = input; +} diff --git a/src/hddsp/pids.h b/src/hddsp/pids.h index 2578229..f14061b 100644 --- a/src/hddsp/pids.h +++ b/src/hddsp/pids.h @@ -1,78 +1,78 @@ -#pragma once - -#include - -#define MAX_LONG_NAME_LEN 56 -#define MAX_LONG_NAME_FRAMES 8 -#define MAX_MESSAGE_LEN 190 -#define MAX_MESSAGE_FRAMES 32 -#define MAX_AUDIO_SERVICES 8 -#define MAX_DATA_SERVICES 16 -#define NUM_PARAMETERS 12 -#define MAX_SLOGAN_LEN 95 -#define MAX_SLOGAN_FRAMES 16 -#define MAX_ALERT_LEN 381 -#define MAX_ALERT_FRAMES 64 - -typedef struct -{ - int access; - int type; - int sound_exp; -} asd_t; - -typedef struct -{ - int access; - int type; - int mime_type; -} dsd_t; - -typedef struct -{ - struct input_t *input; - - char country_code[3]; - int fcc_facility_id; - - char short_name[8]; - - char long_name[MAX_LONG_NAME_LEN + 1]; - uint8_t long_name_have_frame[MAX_LONG_NAME_FRAMES]; - int long_name_seq; - int long_name_displayed; - - float latitude; - float longitude; - int altitude; - - char message[MAX_MESSAGE_LEN + 1]; - uint8_t message_have_frame[MAX_MESSAGE_FRAMES]; - int message_seq; - int message_priority; - int message_encoding; - int message_len; - int message_displayed; - - asd_t audio_services[MAX_AUDIO_SERVICES]; - dsd_t data_services[MAX_DATA_SERVICES]; - - int parameters[NUM_PARAMETERS]; - - char slogan[MAX_SLOGAN_LEN + 1]; - uint8_t slogan_have_frame[MAX_SLOGAN_FRAMES]; - int slogan_encoding; - int slogan_len; - int slogan_displayed; - - char alert[MAX_ALERT_LEN + 1]; - uint8_t alert_have_frame[MAX_ALERT_FRAMES]; - int alert_seq; - int alert_encoding; - int alert_len; - int alert_cnt_len; - int alert_displayed; -} pids_t; - -void pids_frame_push(pids_t *st, uint8_t *bits); -void pids_init(pids_t *st, struct input_t *input); +#pragma once + +#include + +#define MAX_LONG_NAME_LEN 56 +#define MAX_LONG_NAME_FRAMES 8 +#define MAX_MESSAGE_LEN 190 +#define MAX_MESSAGE_FRAMES 32 +#define MAX_AUDIO_SERVICES 8 +#define MAX_DATA_SERVICES 16 +#define NUM_PARAMETERS 12 +#define MAX_SLOGAN_LEN 95 +#define MAX_SLOGAN_FRAMES 16 +#define MAX_ALERT_LEN 381 +#define MAX_ALERT_FRAMES 64 + +typedef struct +{ + int access; + int type; + int sound_exp; +} asd_t; + +typedef struct +{ + int access; + int type; + int mime_type; +} dsd_t; + +typedef struct +{ + struct input_t *input; + + char country_code[3]; + int fcc_facility_id; + + char short_name[8]; + + char long_name[MAX_LONG_NAME_LEN + 1]; + uint8_t long_name_have_frame[MAX_LONG_NAME_FRAMES]; + int long_name_seq; + int long_name_displayed; + + float latitude; + float longitude; + int altitude; + + char message[MAX_MESSAGE_LEN + 1]; + uint8_t message_have_frame[MAX_MESSAGE_FRAMES]; + int message_seq; + int message_priority; + int message_encoding; + int message_len; + int message_displayed; + + asd_t audio_services[MAX_AUDIO_SERVICES]; + dsd_t data_services[MAX_DATA_SERVICES]; + + int parameters[NUM_PARAMETERS]; + + char slogan[MAX_SLOGAN_LEN + 1]; + uint8_t slogan_have_frame[MAX_SLOGAN_FRAMES]; + int slogan_encoding; + int slogan_len; + int slogan_displayed; + + char alert[MAX_ALERT_LEN + 1]; + uint8_t alert_have_frame[MAX_ALERT_FRAMES]; + int alert_seq; + int alert_encoding; + int alert_len; + int alert_cnt_len; + int alert_displayed; +} pids_t; + +void pids_frame_push(pids_t *st, uint8_t *bits); +void pids_init(pids_t *st, struct input_t *input); diff --git a/src/hddsp/private.h b/src/hddsp/private.h index 0e0aed4..11e5f56 100644 --- a/src/hddsp/private.h +++ b/src/hddsp/private.h @@ -1,39 +1,39 @@ -#pragma once - -#include -#include -#include - -#include "nrsc5.h" - -#include "config.h" -#include "defines.h" -#include "input.h" -#include "output.h" - -struct nrsc5_t -{ - int mode; - int closed; - nrsc5_callback_t callback; - void *callback_opaque; - - input_t input; - output_t output; -}; - -void nrsc5_report(nrsc5_t *, const nrsc5_event_t *evt); -void nrsc5_report_lost_device(nrsc5_t *st); -void nrsc5_report_iq(nrsc5_t *, const void *data, size_t count); -void nrsc5_report_sync(nrsc5_t *); -void nrsc5_report_lost_sync(nrsc5_t *); -void nrsc5_report_mer(nrsc5_t *, float lower, float upper); -void nrsc5_report_ber(nrsc5_t *, float cber); -void nrsc5_report_hdc(nrsc5_t *, unsigned int program, const uint8_t *data, size_t count); -void nrsc5_report_audio(nrsc5_t *, unsigned int program, const int16_t *data, size_t count); -void nrsc5_report_lot(nrsc5_t *, uint16_t port, unsigned int lot, unsigned int size, uint32_t mime, const char *name, const uint8_t *data); -void nrsc5_report_sig(nrsc5_t *, sig_service_t *services, unsigned int count); -void nrsc5_report_sis(nrsc5_t *, const char *country_code, int fcc_facility_id, const char *name, - const char *slogan, const char *message, const char *alert, - float latitude, float longitude, int altitude, nrsc5_sis_asd_t *audio_services, - nrsc5_sis_dsd_t *data_services); +#pragma once + +#include +#include +#include + +#include "nrsc5.h" + +#include "config.h" +#include "defines.h" +#include "input.h" +#include "output.h" + +struct nrsc5_t +{ + int mode; + int closed; + nrsc5_callback_t callback; + void *callback_opaque; + + input_t input; + output_t output; +}; + +void nrsc5_report(nrsc5_t *, const nrsc5_event_t *evt); +void nrsc5_report_lost_device(nrsc5_t *st); +void nrsc5_report_iq(nrsc5_t *, const void *data, size_t count); +void nrsc5_report_sync(nrsc5_t *); +void nrsc5_report_lost_sync(nrsc5_t *); +void nrsc5_report_mer(nrsc5_t *, float lower, float upper); +void nrsc5_report_ber(nrsc5_t *, float cber); +void nrsc5_report_hdc(nrsc5_t *, unsigned int program, const uint8_t *data, size_t count); +void nrsc5_report_audio(nrsc5_t *, unsigned int program, const int16_t *data, size_t count); +void nrsc5_report_lot(nrsc5_t *, uint16_t port, unsigned int lot, unsigned int size, uint32_t mime, const char *name, const uint8_t *data); +void nrsc5_report_sig(nrsc5_t *, sig_service_t *services, unsigned int count); +void nrsc5_report_sis(nrsc5_t *, const char *country_code, int fcc_facility_id, const char *name, + const char *slogan, const char *message, const char *alert, + float latitude, float longitude, int altitude, nrsc5_sis_asd_t *audio_services, + nrsc5_sis_dsd_t *data_services); diff --git a/src/hddsp/rs_char.h b/src/hddsp/rs_char.h index a7a98d7..563dc01 100644 --- a/src/hddsp/rs_char.h +++ b/src/hddsp/rs_char.h @@ -1,52 +1,52 @@ -/* Include file to configure the RS codec for character symbols - * - * Copyright 2002, Phil Karn, KA9Q - * May be used under the terms of the GNU General Public License (GPL) - */ - -#define DTYPE unsigned char - -/* Reed-Solomon codec control block */ -struct rs { - unsigned int mm; /* Bits per symbol */ - unsigned int nn; /* Symbols per block (= (1<= rs->nn) { - x -= rs->nn; - x = (x >> rs->mm) + (x & rs->nn); - } - return x; -} -#define MODNN(x) modnn(rs,x) - -#define MM (rs->mm) -#define NN (rs->nn) -#define ALPHA_TO (rs->alpha_to) -#define INDEX_OF (rs->index_of) -#define GENPOLY (rs->genpoly) -#define NROOTS (rs->nroots) -#define FCR (rs->fcr) -#define PRIM (rs->prim) -#define IPRIM (rs->iprim) -#define A0 (NN) - -#define ENCODE_RS encode_rs_char -#define DECODE_RS decode_rs_char -#define INIT_RS init_rs_char -#define FREE_RS free_rs_char - -void ENCODE_RS(void *p,DTYPE *data,DTYPE *parity); -int DECODE_RS(void *p,DTYPE *data,int *eras_pos,int no_eras); -void *INIT_RS(unsigned int symsize,unsigned int gfpoly,unsigned int fcr, - unsigned int prim,unsigned int nroots); -void FREE_RS(void *p); +/* Include file to configure the RS codec for character symbols + * + * Copyright 2002, Phil Karn, KA9Q + * May be used under the terms of the GNU General Public License (GPL) + */ + +#define DTYPE unsigned char + +/* Reed-Solomon codec control block */ +struct rs { + unsigned int mm; /* Bits per symbol */ + unsigned int nn; /* Symbols per block (= (1<= rs->nn) { + x -= rs->nn; + x = (x >> rs->mm) + (x & rs->nn); + } + return x; +} +#define MODNN(x) modnn(rs,x) + +#define MM (rs->mm) +#define NN (rs->nn) +#define ALPHA_TO (rs->alpha_to) +#define INDEX_OF (rs->index_of) +#define GENPOLY (rs->genpoly) +#define NROOTS (rs->nroots) +#define FCR (rs->fcr) +#define PRIM (rs->prim) +#define IPRIM (rs->iprim) +#define A0 (NN) + +#define ENCODE_RS encode_rs_char +#define DECODE_RS decode_rs_char +#define INIT_RS init_rs_char +#define FREE_RS free_rs_char + +void ENCODE_RS(void *p,DTYPE *data,DTYPE *parity); +int DECODE_RS(void *p,DTYPE *data,int *eras_pos,int no_eras); +void *INIT_RS(unsigned int symsize,unsigned int gfpoly,unsigned int fcr, + unsigned int prim,unsigned int nroots); +void FREE_RS(void *p); diff --git a/src/hddsp/rs_decode.c b/src/hddsp/rs_decode.c index 6712f3b..d34276e 100644 --- a/src/hddsp/rs_decode.c +++ b/src/hddsp/rs_decode.c @@ -1,227 +1,227 @@ -/* Reed-Solomon decoder - * Copyright 2002 Phil Karn, KA9Q - * May be used under the terms of the GNU General Public License (GPL) - */ - -#include -#include - -#ifndef NULL -#define NULL ((void *)0) -#endif - -#ifndef min -#define min(a,b) ((a) < (b) ? (a) : (b)) -#endif - -#include "rs_char.h" - -int DECODE_RS( -void *p, -DTYPE *data, int *eras_pos, int no_eras){ - - struct rs *rs = (struct rs *)p; - int deg_lambda, el, deg_omega; - int i, j, r, k; - DTYPE u,q,tmp,num1,num2,den,discr_r; - DTYPE* lambda = malloc(sizeof(DTYPE) * (NROOTS + 1)); - DTYPE* s = malloc(sizeof(DTYPE) * NROOTS); - DTYPE* b = malloc(sizeof(DTYPE) * (NROOTS + 1)); - DTYPE* t = malloc(sizeof(DTYPE) * (NROOTS + 1)); - DTYPE* omega = malloc(sizeof(DTYPE) * (NROOTS + 1)); - DTYPE* root = malloc(sizeof(DTYPE) * NROOTS); - DTYPE* reg = malloc(sizeof(DTYPE) * (NROOTS + 1)); - DTYPE* loc = malloc(sizeof(DTYPE) * NROOTS); - int syn_error, count; - - /* form the syndromes; i.e., evaluate data(x) at roots of g(x) */ - for(i=0;(unsigned int)i 0) { - /* Init lambda to be the erasure locator polynomial */ - lambda[1] = ALPHA_TO[MODNN(PRIM*(NN-1-eras_pos[0]))]; - for (i = 1; i < no_eras; i++) { - u = MODNN(PRIM*(NN-1-eras_pos[i])); - for (j = i+1; j > 0; j--) { - tmp = INDEX_OF[lambda[j - 1]]; - if(tmp != A0) - lambda[j] ^= ALPHA_TO[MODNN(u + tmp)]; - } - } - - } - for(i=0;(unsigned int)i 0; j--){ - if (reg[j] != A0) { - reg[j] = MODNN(reg[j] + j); - q ^= ALPHA_TO[reg[j]]; - } - } - if (q != 0) - continue; /* Not a root */ - /* store root (index-form) and error location number */ - root[count] = i; - loc[count] = k; - /* If we've already found max possible roots, - * abort the search to save time - */ - if(++count == deg_lambda) - break; - } - if (deg_lambda != count) { - /* - * deg(lambda) unequal to number of roots => uncorrectable - * error detected - */ - count = -1; - goto finish; - } - /* - * Compute err+eras evaluator poly omega(x) = s(x)*lambda(x) (modulo - * x**NROOTS). in index form. Also find deg(omega). - */ - deg_omega = 0; - for (i = 0; (unsigned int)i < NROOTS;i++){ - tmp = 0; - j = (deg_lambda < i) ? deg_lambda : i; - for(;j >= 0; j--){ - if ((s[i - j] != A0) && (lambda[j] != A0)) - tmp ^= ALPHA_TO[MODNN(s[i - j] + lambda[j])]; - } - if(tmp != 0) - deg_omega = i; - omega[i] = INDEX_OF[tmp]; - } - omega[NROOTS] = A0; - - /* - * Compute error values in poly-form. num1 = omega(inv(X(l))), num2 = - * inv(X(l))**(FCR-1) and den = lambda_pr(inv(X(l))) all in poly-form - */ - for (j = count-1; j >=0; j--) { - num1 = 0; - for (i = deg_omega; i >= 0; i--) { - if (omega[i] != A0) - num1 ^= ALPHA_TO[MODNN(omega[i] + i * root[j])]; - } - num2 = ALPHA_TO[MODNN(root[j] * (FCR - 1) + NN)]; - den = 0; - - /* lambda[i+1] for i even is the formal derivative lambda_pr of lambda[i] */ - for (i = (int)min((unsigned int)deg_lambda,NROOTS-1) & ~1; i >= 0; i -=2) { - if(lambda[i+1] != A0) - den ^= ALPHA_TO[MODNN(lambda[i+1] + i * root[j])]; - } - if (den == 0) { - count = -1; - goto finish; - } - /* Apply error to data */ - if (num1 != 0) { - data[loc[j]] ^= ALPHA_TO[MODNN(INDEX_OF[num1] + INDEX_OF[num2] + NN - INDEX_OF[den])]; - } - } - finish: - if(eras_pos != NULL){ - for(i=0;i +#include + +#ifndef NULL +#define NULL ((void *)0) +#endif + +#ifndef min +#define min(a,b) ((a) < (b) ? (a) : (b)) +#endif + +#include "rs_char.h" + +int DECODE_RS( +void *p, +DTYPE *data, int *eras_pos, int no_eras){ + + struct rs *rs = (struct rs *)p; + int deg_lambda, el, deg_omega; + int i, j, r, k; + DTYPE u,q,tmp,num1,num2,den,discr_r; + DTYPE* lambda = malloc(sizeof(DTYPE) * (NROOTS + 1)); + DTYPE* s = malloc(sizeof(DTYPE) * NROOTS); + DTYPE* b = malloc(sizeof(DTYPE) * (NROOTS + 1)); + DTYPE* t = malloc(sizeof(DTYPE) * (NROOTS + 1)); + DTYPE* omega = malloc(sizeof(DTYPE) * (NROOTS + 1)); + DTYPE* root = malloc(sizeof(DTYPE) * NROOTS); + DTYPE* reg = malloc(sizeof(DTYPE) * (NROOTS + 1)); + DTYPE* loc = malloc(sizeof(DTYPE) * NROOTS); + int syn_error, count; + + /* form the syndromes; i.e., evaluate data(x) at roots of g(x) */ + for(i=0;(unsigned int)i 0) { + /* Init lambda to be the erasure locator polynomial */ + lambda[1] = ALPHA_TO[MODNN(PRIM*(NN-1-eras_pos[0]))]; + for (i = 1; i < no_eras; i++) { + u = MODNN(PRIM*(NN-1-eras_pos[i])); + for (j = i+1; j > 0; j--) { + tmp = INDEX_OF[lambda[j - 1]]; + if(tmp != A0) + lambda[j] ^= ALPHA_TO[MODNN(u + tmp)]; + } + } + + } + for(i=0;(unsigned int)i 0; j--){ + if (reg[j] != A0) { + reg[j] = MODNN(reg[j] + j); + q ^= ALPHA_TO[reg[j]]; + } + } + if (q != 0) + continue; /* Not a root */ + /* store root (index-form) and error location number */ + root[count] = i; + loc[count] = k; + /* If we've already found max possible roots, + * abort the search to save time + */ + if(++count == deg_lambda) + break; + } + if (deg_lambda != count) { + /* + * deg(lambda) unequal to number of roots => uncorrectable + * error detected + */ + count = -1; + goto finish; + } + /* + * Compute err+eras evaluator poly omega(x) = s(x)*lambda(x) (modulo + * x**NROOTS). in index form. Also find deg(omega). + */ + deg_omega = 0; + for (i = 0; (unsigned int)i < NROOTS;i++){ + tmp = 0; + j = (deg_lambda < i) ? deg_lambda : i; + for(;j >= 0; j--){ + if ((s[i - j] != A0) && (lambda[j] != A0)) + tmp ^= ALPHA_TO[MODNN(s[i - j] + lambda[j])]; + } + if(tmp != 0) + deg_omega = i; + omega[i] = INDEX_OF[tmp]; + } + omega[NROOTS] = A0; + + /* + * Compute error values in poly-form. num1 = omega(inv(X(l))), num2 = + * inv(X(l))**(FCR-1) and den = lambda_pr(inv(X(l))) all in poly-form + */ + for (j = count-1; j >=0; j--) { + num1 = 0; + for (i = deg_omega; i >= 0; i--) { + if (omega[i] != A0) + num1 ^= ALPHA_TO[MODNN(omega[i] + i * root[j])]; + } + num2 = ALPHA_TO[MODNN(root[j] * (FCR - 1) + NN)]; + den = 0; + + /* lambda[i+1] for i even is the formal derivative lambda_pr of lambda[i] */ + for (i = (int)min((unsigned int)deg_lambda,NROOTS-1) & ~1; i >= 0; i -=2) { + if(lambda[i+1] != A0) + den ^= ALPHA_TO[MODNN(lambda[i+1] + i * root[j])]; + } + if (den == 0) { + count = -1; + goto finish; + } + /* Apply error to data */ + if (num1 != 0) { + data[loc[j]] ^= ALPHA_TO[MODNN(INDEX_OF[num1] + INDEX_OF[num2] + NN - INDEX_OF[den])]; + } + } + finish: + if(eras_pos != NULL){ + for(i=0;i - -#include "rs_char.h" - -#ifndef NULL -#define NULL ((void *)0) -#endif - -void FREE_RS(void *p){ - struct rs *rs = (struct rs *)p; - - free(rs->alpha_to); - free(rs->index_of); - free(rs->genpoly); - free(rs->modnn_table); - free(rs); -} - -/* Initialize a Reed-Solomon codec - * symsize = symbol size, bits (1-8) - * gfpoly = Field generator polynomial coefficients - * fcr = first root of RS code generator polynomial, index form - * prim = primitive element to generate polynomial roots - * nroots = RS code generator polynomial degree (number of roots) - */ -void *INIT_RS(unsigned int symsize,unsigned int gfpoly,unsigned fcr,unsigned prim, - unsigned int nroots){ - struct rs *rs; - int sr,root,iprim; - unsigned int i, j; - - if(symsize > 8*sizeof(DTYPE)) - return NULL; /* Need version with ints rather than chars */ - - if(fcr >= (1u<= (1u<= (1u<mm = symsize; - rs->nn = (1<alpha_to = (DTYPE *)malloc(sizeof(DTYPE)*(rs->nn+1)); - if(rs->alpha_to == NULL){ - free(rs); - return NULL; - } - rs->index_of = (DTYPE *)malloc(sizeof(DTYPE)*(rs->nn+1)); - if(rs->index_of == NULL){ - free(rs->alpha_to); - free(rs); - return NULL; - } - - /* Generate Galois field lookup tables */ - rs->index_of[0] = A0; /* log(zero) = -inf */ - rs->alpha_to[A0] = 0; /* alpha**-inf = 0 */ - sr = 1; - for(i=0;inn;i++){ - rs->index_of[sr] = i; - rs->alpha_to[i] = sr; - sr <<= 1; - if(sr & (1<nn; - } - if(sr != 1){ - /* field generator polynomial is not primitive! */ - free(rs->alpha_to); - free(rs->index_of); - free(rs); - return NULL; - } - - /* Form RS code generator polynomial from its roots */ - rs->genpoly = (DTYPE *)malloc(sizeof(DTYPE)*(nroots+1)); - if(rs->genpoly == NULL){ - free(rs->alpha_to); - free(rs->index_of); - free(rs); - return NULL; - } - rs->fcr = fcr; - rs->prim = prim; - rs->nroots = nroots; - - /* Find prim-th root of 1, used in decoding */ - for(iprim=1;(iprim % prim) != 0;iprim += rs->nn) - ; - rs->iprim = iprim / prim; - - rs->genpoly[0] = 1; - for (i = 0,root=fcr*prim; i < nroots; i++,root += prim) { - rs->genpoly[i+1] = 1; - - /* Multiply rs->genpoly[] by @**(root + x) */ - for (j = i; j > 0; j--){ - if (rs->genpoly[j] != 0) - rs->genpoly[j] = rs->genpoly[j-1] ^ rs->alpha_to[modnn(rs,rs->index_of[rs->genpoly[j]] + root)]; - else - rs->genpoly[j] = rs->genpoly[j-1]; - } - /* rs->genpoly[0] can never be zero */ - rs->genpoly[0] = rs->alpha_to[modnn(rs,rs->index_of[rs->genpoly[0]] + root)]; - } - /* convert rs->genpoly[] to index form for quicker encoding */ - for (i = 0; i <= nroots; i++) - rs->genpoly[i] = rs->index_of[rs->genpoly[i]]; - - /* Form modnn lookup table */ - rs->modnn_table = (int *)malloc(sizeof(int)*(2<<((sizeof(unsigned char))*8))); - if(rs->modnn_table == NULL){ - free(rs->genpoly); - free(rs->alpha_to); - free(rs->index_of); - free(rs); - return NULL; - } - for(i = 0; i < (2<<((sizeof(unsigned char))*8)); i++){ - j = i; - rs->modnn_table[i] = modnn(rs,j); - } - - return rs; -} +/* Initialize a RS codec + * + * Copyright 2002 Phil Karn, KA9Q + * May be used under the terms of the GNU General Public License (GPL) + */ +#include + +#include "rs_char.h" + +#ifndef NULL +#define NULL ((void *)0) +#endif + +void FREE_RS(void *p){ + struct rs *rs = (struct rs *)p; + + free(rs->alpha_to); + free(rs->index_of); + free(rs->genpoly); + free(rs->modnn_table); + free(rs); +} + +/* Initialize a Reed-Solomon codec + * symsize = symbol size, bits (1-8) + * gfpoly = Field generator polynomial coefficients + * fcr = first root of RS code generator polynomial, index form + * prim = primitive element to generate polynomial roots + * nroots = RS code generator polynomial degree (number of roots) + */ +void *INIT_RS(unsigned int symsize,unsigned int gfpoly,unsigned fcr,unsigned prim, + unsigned int nroots){ + struct rs *rs; + int sr,root,iprim; + unsigned int i, j; + + if(symsize > 8*sizeof(DTYPE)) + return NULL; /* Need version with ints rather than chars */ + + if(fcr >= (1u<= (1u<= (1u<mm = symsize; + rs->nn = (1<alpha_to = (DTYPE *)malloc(sizeof(DTYPE)*(rs->nn+1)); + if(rs->alpha_to == NULL){ + free(rs); + return NULL; + } + rs->index_of = (DTYPE *)malloc(sizeof(DTYPE)*(rs->nn+1)); + if(rs->index_of == NULL){ + free(rs->alpha_to); + free(rs); + return NULL; + } + + /* Generate Galois field lookup tables */ + rs->index_of[0] = A0; /* log(zero) = -inf */ + rs->alpha_to[A0] = 0; /* alpha**-inf = 0 */ + sr = 1; + for(i=0;inn;i++){ + rs->index_of[sr] = i; + rs->alpha_to[i] = sr; + sr <<= 1; + if(sr & (1<nn; + } + if(sr != 1){ + /* field generator polynomial is not primitive! */ + free(rs->alpha_to); + free(rs->index_of); + free(rs); + return NULL; + } + + /* Form RS code generator polynomial from its roots */ + rs->genpoly = (DTYPE *)malloc(sizeof(DTYPE)*(nroots+1)); + if(rs->genpoly == NULL){ + free(rs->alpha_to); + free(rs->index_of); + free(rs); + return NULL; + } + rs->fcr = fcr; + rs->prim = prim; + rs->nroots = nroots; + + /* Find prim-th root of 1, used in decoding */ + for(iprim=1;(iprim % prim) != 0;iprim += rs->nn) + ; + rs->iprim = iprim / prim; + + rs->genpoly[0] = 1; + for (i = 0,root=fcr*prim; i < nroots; i++,root += prim) { + rs->genpoly[i+1] = 1; + + /* Multiply rs->genpoly[] by @**(root + x) */ + for (j = i; j > 0; j--){ + if (rs->genpoly[j] != 0) + rs->genpoly[j] = rs->genpoly[j-1] ^ rs->alpha_to[modnn(rs,rs->index_of[rs->genpoly[j]] + root)]; + else + rs->genpoly[j] = rs->genpoly[j-1]; + } + /* rs->genpoly[0] can never be zero */ + rs->genpoly[0] = rs->alpha_to[modnn(rs,rs->index_of[rs->genpoly[0]] + root)]; + } + /* convert rs->genpoly[] to index form for quicker encoding */ + for (i = 0; i <= nroots; i++) + rs->genpoly[i] = rs->index_of[rs->genpoly[i]]; + + /* Form modnn lookup table */ + rs->modnn_table = (int *)malloc(sizeof(int)*(2<<((sizeof(unsigned char))*8))); + if(rs->modnn_table == NULL){ + free(rs->genpoly); + free(rs->alpha_to); + free(rs->index_of); + free(rs); + return NULL; + } + for(i = 0; i < (2<<((sizeof(unsigned char))*8)); i++){ + j = i; + rs->modnn_table[i] = modnn(rs,j); + } + + return rs; +} diff --git a/src/hddsp/strndup.c b/src/hddsp/strndup.c index 7e8bf02..e6dbfbb 100644 --- a/src/hddsp/strndup.c +++ b/src/hddsp/strndup.c @@ -1,32 +1,32 @@ -/* A replacement function, for systems that lack strndup. - Copyright (C) 1996-1998, 2001-2003, 2005-2007, 2009-2017 Free Software - Foundation, Inc. - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by the - Free Software Foundation; either version 2, or (at your option) any - later version. - This program 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 General Public License for more details. - You should have received a copy of the GNU General Public License - along with this program; if not, see . */ - -#include "config.h" - -#include -#include - -#ifndef HAVE_STRNDUP -char *strndup (char const *s, size_t n) -{ - size_t len = strnlen (s, n); - char *new = malloc (len + 1); - - if (new == NULL) - return NULL; - - new[len] = '\0'; - return memcpy (new, s, len); -} -#endif +/* A replacement function, for systems that lack strndup. + Copyright (C) 1996-1998, 2001-2003, 2005-2007, 2009-2017 Free Software + Foundation, Inc. + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; either version 2, or (at your option) any + later version. + This program 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 General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program; if not, see . */ + +#include "config.h" + +#include +#include + +#ifndef HAVE_STRNDUP +char *strndup (char const *s, size_t n) +{ + size_t len = strnlen (s, n); + char *new = malloc (len + 1); + + if (new == NULL) + return NULL; + + new[len] = '\0'; + return memcpy (new, s, len); +} +#endif diff --git a/src/hddsp/sync.c b/src/hddsp/sync.c index 8e01243..8ef9259 100644 --- a/src/hddsp/sync.c +++ b/src/hddsp/sync.c @@ -1,679 +1,679 @@ -/* - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "config.h" - -#ifdef _MSC_VER -#define _USE_MATH_DEFINES -#endif - -#include -#include - -#include "defines.h" -#include "input.h" -#include "private.h" -#include "sync.h" - -#define PM_PARTITIONS 10 -#define MAX_PARTITIONS 14 -#define PARTITION_DATA_CARRIERS 18 -#define PARTITION_WIDTH 19 -#define MIDDLE_REF_SC 30 // midpoint of Table 11-3 in 1011s.pdf - -static uint8_t gray4(float f) -{ - if (f < -1) - return 0; - else if (f < 0) - return 2; - else if (f < 1) - return 3; - else - return 1; -} - -static uint8_t gray8(float f) -{ - if (f < -3) - return 0; - else if (f < -2) - return 4; - else if (f < -1) - return 6; - else if (f < 0) - return 2; - else if (f < 1) - return 3; - else if (f < 2) - return 7; - else if (f < 3) - return 5; - else - return 1; -} - -static uint8_t qpsk(fcomplex_t cf) -{ - return (crealf(cf) < 0 ? 0 : 1) | (cimagf(cf) < 0 ? 0 : 2); -} - -static uint8_t qam16(fcomplex_t cf) -{ - return gray4(crealf(cf)) | (gray4(cimagf(cf)) << 2); -} - -static uint8_t qam64(fcomplex_t cf) -{ - return gray8(crealf(cf)) | (gray8(cimagf(cf)) << 3); -} - -static void adjust_ref(sync_t *st, unsigned int ref, int cfo) -{ - unsigned int n; - float cfo_freq = 2 * M_PI * cfo * CP_FM / FFT_FM; - - // sync bits (after DBPSK) - static const signed char sync[] = { - -1, 1, -1, -1, -1, 1, 1 - }; - - for (n = 0; n < BLKSZ; n++) - { - float error = cargf(CMPLXFMUL(CMPLXFMUL(st->buffer[ref][n], st->buffer[ref][n]), cexpf(CMPLXFMULF(CMPLXFNEG(I), 2 * st->costas_phase[ref])))) * 0.5; - - st->phases[ref][n] = st->costas_phase[ref]; - st->buffer[ref][n] = CMPLXFMUL(st->buffer[ref][n], cexpf(CMPLXFMULF(CMPLXFNEG(I), st->costas_phase[ref]))); - - st->costas_freq[ref] += st->beta * error; - if (st->costas_freq[ref] > 0.5) st->costas_freq[ref] = 0.5; - if (st->costas_freq[ref] < -0.5) st->costas_freq[ref] = -0.5; - st->costas_phase[ref] += st->costas_freq[ref] + cfo_freq + (st->alpha * error); - if (st->costas_phase[ref] > M_PI) st->costas_phase[ref] -= 2 * M_PI; - if (st->costas_phase[ref] < -M_PI) st->costas_phase[ref] += 2 * M_PI; - } - - // compare to sync bits - float x = 0; - for (n = 0; n < sizeof(sync); n++) - x += crealf(st->buffer[ref][n]) * sync[n]; - if (x < 0) - { - // adjust phase by pi to compensate - for (n = 0; n < BLKSZ; n++) - { - st->phases[ref][n] += M_PI; - st->buffer[ref][n] = CMPLXFMULF(st->buffer[ref][n], -1); - } - st->costas_phase[ref] += M_PI; - } -} - -static void reset_ref(sync_t *st, unsigned int ref) -{ - for (unsigned int n = 0; n < BLKSZ; n++) - st->buffer[ref][n] = CMPLXFMUL(st->buffer[ref][n], cexpf(CMPLXFMULF(I, st->phases[ref][n]))); -} - -static void decode_dbpsk(const fcomplex_t *buf, unsigned char *data, int size) -{ - unsigned char prev = 0; - - for (int n = 0; n < size; n++) - { - unsigned char bit = crealf(buf[n]) <= 0 ? 0 : 1; - data[n] = bit ^ prev; - prev = bit; - } -} - -static int fuzzy_match(const signed char *needle, unsigned int needle_size, const unsigned char *data, int size) -{ - for (int n = 0; n < size; n++) - { - unsigned int i; - for (i = 0; i < needle_size; i++) - { - // first bit of data may be wrong, so ignore - if ((n + i) % size == 0) continue; - // ignore don't care bits - if (needle[i] < 0) continue; - // test if bit is correct - if (needle[i] != data[(n + i) % size]) - break; - } - if (i == needle_size) - return n; - } - return -1; -} - -static int find_first_block(sync_t *st, unsigned int ref, unsigned int rsid) -{ - signed char needle[] = { - 0, 1, 1, 0, 0, 1, 0, -1, -1, 1, rsid >> 1, rsid & 1, 0, (rsid >> 1) ^ (rsid & 1), 0, -1, 0, 0, 0, 0, -1, 1, 1, 1 - }; - unsigned char data[BLKSZ]; - int n; - - decode_dbpsk(st->buffer[ref], data, BLKSZ); - n = fuzzy_match(needle, sizeof(needle), data, BLKSZ); - if (n == 0) - st->psmi = (data[25] << 5) | (data[26] << 4) | (data[27] << 3) | (data[28] << 2) | (data[29] << 1) | data[30]; - return n; -} - -static int find_ref(sync_t *st, unsigned int ref, unsigned int rsid) -{ - signed char needle[] = { - 0, 1, 1, 0, 0, 1, 0, -1, -1, 1, rsid >> 1, rsid & 1, 0, (rsid >> 1) ^ (rsid & 1), 0, -1, -1, -1, -1, -1, -1, 1, 1, 1 - }; - unsigned char data[BLKSZ]; - - decode_dbpsk(st->buffer[ref], data, BLKSZ); - return fuzzy_match(needle, sizeof(needle), data, BLKSZ); -} - -static int find_block_am(sync_t *st, unsigned int ref) -{ - signed char needle[] = { - 0, 1, 1, 0, 0, 1, 0, -1, -1, 1, -1, -1, -1, -1, 0, -1, -1, -1, -1, -1, -1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1 - }; - unsigned char data[BLKSZ]; - - for (int n = 0; n < BLKSZ; n++) - { - data[n] = cimagf(st->buffer[ref][n]) <= 0 ? 0 : 1; - if ((needle[n] >= 0) && (data[n] != needle[n])) return -1; - } - - // parity checks - if (data[7] ^ data[8]) return -1; - if (data[10] ^ data[11] ^ data[12] ^ data[13]) return -1; - if (data[15] ^ data[16] ^ data[17] ^ data[18] ^ data[19] ^ data[20]) return -1; - if (data[23] ^ data[24] ^ data[25] ^ data[26] ^ data[27] ^ data[28] ^ data[29] ^ data[30] ^ data[31]) return -1; - - return (data[17] << 2) | (data[18] << 1) | data[19]; -} - -static int find_ref_am(sync_t *st, unsigned int ref) -{ - signed char needle[] = { - 0, 1, 1, 0, 0, 1, 0, -1, -1, 1, -1, -1, -1, -1, 0, -1, -1, -1, -1, -1, -1, 1, 1 - }; - unsigned char data[BLKSZ]; - - for (int n = 0; n < BLKSZ; n++) - data[n] = cimagf(st->buffer[ref][n]) <= 0 ? 0 : 1; - - return fuzzy_match(needle, sizeof(needle), data, BLKSZ); -} - -static float calc_smag(sync_t *st, unsigned int ref) -{ - float sum = 0; - // phase was already corrected, so imaginary component is zero - for (int n = 0; n < BLKSZ; n++) - sum += fabsf(crealf(st->buffer[ref][n])); - return sum / BLKSZ; -} - -static void adjust_data(sync_t *st, unsigned int lower, unsigned int upper) -{ - float smag0, smag19; - smag0 = calc_smag(st, lower); - smag19 = calc_smag(st, upper); - - for (int n = 0; n < BLKSZ; n++) - { - fcomplex_t upper_phase = cexpf(CMPLXFMULF(I, st->phases[upper][n])); - fcomplex_t lower_phase = cexpf(CMPLXFMULF(I, st->phases[lower][n])); - - for (int k = 1; k < PARTITION_WIDTH; k++) - { - // average phase difference - fcomplex_t C = CMPLXFDIV(CMPLXF(PARTITION_WIDTH, PARTITION_WIDTH), (CMPLXFADD(CMPLXFMULF(upper_phase, k * smag19), CMPLXFMULF(lower_phase, (PARTITION_WIDTH - k) * smag0)))); - // adjust sample - st->buffer[lower + k][n] = CMPLXFMUL(st->buffer[lower + k][n], C); - } - } -} - -float phase_diff(float a, float b) -{ - float diff = a - b; - while (diff > M_PI / 2) diff -= M_PI; - while (diff < -M_PI / 2) diff += M_PI; - return diff; -} - -void detect_cfo(sync_t *st) -{ - for (int cfo = -2 * PARTITION_WIDTH; cfo < 2 * PARTITION_WIDTH; cfo++) - { - int offset; - int best_offset = -1; - unsigned int best_count = 0; - unsigned int offset_count[BLKSZ]; - - memset(offset_count, 0, BLKSZ * sizeof(unsigned int)); - - for (int i = 0; i <= PM_PARTITIONS; i++) - { - adjust_ref(st, cfo + LB_START + i * PARTITION_WIDTH, cfo); - offset = find_ref(st, cfo + LB_START + i * PARTITION_WIDTH, (MIDDLE_REF_SC-i) & 0x3); - reset_ref(st, cfo + LB_START + i * PARTITION_WIDTH); - if (offset >= 0) - offset_count[offset]++; - - adjust_ref(st, cfo + UB_END - i * PARTITION_WIDTH, cfo); - offset = find_ref(st, cfo + UB_END - i * PARTITION_WIDTH, (MIDDLE_REF_SC-i) & 0x3); - reset_ref(st, cfo + UB_END - i * PARTITION_WIDTH); - if (offset >= 0) - offset_count[offset]++; - } - - for (offset = 0; offset < BLKSZ; offset++) - { - if (offset_count[offset] > best_count) { - best_offset = offset; - best_count = offset_count[offset]; - } - } - - if (best_offset >= 0 && best_count >= 3) - { - // At least three offsets matched, so this is likely the correct CFO. - input_set_skip(st->input, best_offset * FFTCP_FM); - acquire_cfo_adjust(&st->input->acq, cfo); - - log_debug("Block @ %d", best_offset); - - // Wait until the buffers have cleared before measuring again. - st->cfo_wait = 8; - break; - } - } -} - -void sync_process_fm(sync_t *st) -{ - int i, partitions_per_band; - - switch (st->psmi) { - case 2: - partitions_per_band = 11; - break; - case 3: - partitions_per_band = 12; - break; - case 5: - case 6: - case 11: - partitions_per_band = 14; - break; - default: - partitions_per_band = 10; - } - - for (i = 0; i < partitions_per_band * PARTITION_WIDTH + 1; i += PARTITION_WIDTH) - { - adjust_ref(st, LB_START + i, 0); - adjust_ref(st, UB_END - i, 0); - } - - // check if we now have synchronization - if (st->input->sync_state == SYNC_STATE_COARSE) - { - unsigned int good_refs = 0; - for (i = 0; i <= partitions_per_band; i++) - { - if (find_first_block(st, LB_START + i * PARTITION_WIDTH, (MIDDLE_REF_SC-i) & 0x3) == 0) - good_refs++; - if (find_first_block(st, UB_END - i * PARTITION_WIDTH, (MIDDLE_REF_SC-i) & 0x3) == 0) - good_refs++; - } - - if (good_refs >= 4) - { - input_set_sync_state(st->input, SYNC_STATE_FINE); - decode_reset(&st->input->decode); - frame_reset(&st->input->frame); - } - else if (st->cfo_wait == 0) - { - detect_cfo(st); - } - else - { - // Decrease wait counter. - st->cfo_wait--; - } - } - - // if we are still synchronized - if (st->input->sync_state == SYNC_STATE_FINE) - { - float samperr = 0, angle = 0; - float sum_xy = 0, sum_x2 = 0; - for (i = 0; i < partitions_per_band * PARTITION_WIDTH; i += PARTITION_WIDTH) - { - adjust_data(st, LB_START + i, LB_START + i + PARTITION_WIDTH); - adjust_data(st, UB_END - i - PARTITION_WIDTH, UB_END - i); - - samperr += phase_diff(st->phases[LB_START + i][0], st->phases[LB_START + i + PARTITION_WIDTH][0]); - samperr += phase_diff(st->phases[UB_END - i - PARTITION_WIDTH][0], st->phases[UB_END - i][0]); - } - samperr = samperr / (partitions_per_band * 2) * FFT_FM / PARTITION_WIDTH / (2 * M_PI); - - for (i = 0; i < partitions_per_band * PARTITION_WIDTH + 1; i += PARTITION_WIDTH) - { - float x, y; - - x = LB_START + i - (FFT_FM / 2); - y = st->costas_freq[LB_START + i]; - angle += y; - sum_xy += x * y; - sum_x2 += x * x; - - x = UB_END - i - (FFT_FM / 2); - y = st->costas_freq[UB_END - i]; - angle += y; - sum_xy += x * y; - sum_x2 += x * x; - } - samperr -= (sum_xy / sum_x2) * FFT_FM / (2 * M_PI) * ACQUIRE_SYMBOLS; - st->samperr = roundf(samperr); - - angle /= (partitions_per_band + 1) * 2; - st->angle = angle; - - // Calculate modulation error - float error_lb = 0, error_ub = 0; - for (int n = 0; n < BLKSZ; n++) - { - fcomplex_t c, ideal; - for (i = 0; i < partitions_per_band * PARTITION_WIDTH; i += PARTITION_WIDTH) - { - unsigned int j; - for (j = 1; j < PARTITION_WIDTH; j++) - { - c = st->buffer[LB_START + i + j][n]; - ideal = CMPLXF(crealf(c) >= 0 ? 1 : -1, cimagf(c) >= 0 ? 1 : -1); - error_lb += normf(CMPLXFSUB(ideal, c)); - - c = st->buffer[UB_END - i - PARTITION_WIDTH + j][n]; - ideal = CMPLXF(crealf(c) >= 0 ? 1 : -1, cimagf(c) >= 0 ? 1 : -1); - error_ub += normf(CMPLXFSUB(ideal, c)); - } - } - } - - st->error_lb += error_lb; - st->error_ub += error_ub; - - // Display average MER for each sideband - if (++st->mer_cnt == 16) - { - float signal = 2 * BLKSZ * (partitions_per_band * PARTITION_DATA_CARRIERS) * st->mer_cnt; - float mer_db_lb = 10 * log10f(signal / st->error_lb); - float mer_db_ub = 10 * log10f(signal / st->error_ub); - - nrsc5_report_mer(st->input->radio, mer_db_lb, mer_db_ub); - - st->mer_cnt = 0; - st->error_lb = 0; - st->error_ub = 0; - } - - // Soft demod based on MER for each sideband - float mer_lb = 2 * BLKSZ * (partitions_per_band * PARTITION_DATA_CARRIERS) / error_lb; - float mer_ub = 2 * BLKSZ * (partitions_per_band * PARTITION_DATA_CARRIERS) / error_ub; - float mult_lb = fmaxf(fminf(mer_lb * 10, 127), 1); - float mult_ub = fmaxf(fminf(mer_ub * 10, 127), 1); - -#define DEMOD(x) ((x) >= 0 ? 1 : -1) - for (int n = 0; n < BLKSZ; n++) - { - fcomplex_t c; - for (i = LB_START; i < LB_START + (PM_PARTITIONS * PARTITION_WIDTH); i += PARTITION_WIDTH) - { - unsigned int j; - for (j = 1; j < PARTITION_WIDTH; j++) - { - c = st->buffer[i + j][n]; - decode_push_pm(&st->input->decode, DEMOD(crealf(c)) * mult_lb); - decode_push_pm(&st->input->decode, DEMOD(cimagf(c)) * mult_lb); - } - } - for (i = UB_END - (PM_PARTITIONS * PARTITION_WIDTH); i < UB_END; i += PARTITION_WIDTH) - { - unsigned int j; - for (j = 1; j < PARTITION_WIDTH; j++) - { - c = st->buffer[i + j][n]; - decode_push_pm(&st->input->decode, DEMOD(crealf(c)) * mult_ub); - decode_push_pm(&st->input->decode, DEMOD(cimagf(c)) * mult_ub); - } - } - if (st->psmi == 3) { - for (i = LB_START + (PM_PARTITIONS * PARTITION_WIDTH); i < LB_START + (PM_PARTITIONS + 2) * PARTITION_WIDTH; i += PARTITION_WIDTH) - { - unsigned int j; - for (j = 1; j < PARTITION_WIDTH; j++) - { - c = st->buffer[i + j][n]; - decode_push_px1(&st->input->decode, DEMOD(crealf(c)) * mult_lb); - decode_push_px1(&st->input->decode, DEMOD(cimagf(c)) * mult_lb); - } - } - for (i = UB_END - (PM_PARTITIONS + 2) * PARTITION_WIDTH; i < UB_END - (PM_PARTITIONS * PARTITION_WIDTH); i += PARTITION_WIDTH) - { - unsigned int j; - for (j = 1; j < PARTITION_WIDTH; j++) - { - c = st->buffer[i + j][n]; - decode_push_px1(&st->input->decode, DEMOD(crealf(c)) * mult_ub); - decode_push_px1(&st->input->decode, DEMOD(cimagf(c)) * mult_ub); - } - } - } - } - } -} - -void sync_process_am(sync_t *st) -{ - int offset; - - for (int i = REF_INDEX_AM; i <= PIDS_2_INDEX_AM; i++) - { - for (int n = 0; n < BLKSZ; n++) - { - st->buffer[CENTER_AM + i][n] = CMPLXFSUB(st->buffer[CENTER_AM + i][n], conjf(st->buffer[CENTER_AM - i][n])); - } - } - - for (int i = PRIMARY_INDEX_AM; i <= MAX_INDEX_AM; i++) - { - for (int n = 0; n < BLKSZ; n++) - { - st->buffer[CENTER_AM - i][n] = CMPLXFNEG(conjf(st->buffer[CENTER_AM - i][n])); - } - } - - if (st->input->sync_state == SYNC_STATE_COARSE && st->cfo_wait == 0) - { - offset = find_ref_am(st, CENTER_AM + REF_INDEX_AM); - if (offset > 0) - { - input_set_skip(st->input, offset * FFTCP_AM); - log_debug("Block @ %d", offset); - st->cfo_wait = 8; - } - } - else - { - st->cfo_wait--; - } - - if (st->input->sync_state == SYNC_STATE_COARSE) - { - int bc = find_block_am(st, CENTER_AM + REF_INDEX_AM); - - if (bc == -1) - st->offset_history = 0; - else - st->offset_history = (st->offset_history << 4) | bc; - - if ((st->offset_history & 0xffff) == 0x5670) - { - log_debug("Sync!"); - st->input->sync_state = SYNC_STATE_FINE; - decode_reset(&st->input->decode); - frame_reset(&st->input->frame); - st->offset_history = 0; - } - } - - if (st->input->sync_state == SYNC_STATE_FINE) - { - fcomplex_t pids1_mult = CMPLXFDIV(CMPLXFMULF(CMPLXF(1.5, -0.5), 2), (CMPLXFADD(st->buffer[CENTER_AM + PIDS_1_INDEX_AM][8], st->buffer[CENTER_AM + PIDS_1_INDEX_AM][24]))); - fcomplex_t pids2_mult = CMPLXFDIV(CMPLXFMULF(CMPLXF(1.5, -0.5), 2), (CMPLXFADD(st->buffer[CENTER_AM + PIDS_2_INDEX_AM][8], st->buffer[CENTER_AM + PIDS_2_INDEX_AM][24]))); - - for (int n = 0; n < BLKSZ; n++) - { - st->buffer[CENTER_AM + PIDS_1_INDEX_AM][n] = CMPLXFMUL(st->buffer[CENTER_AM + PIDS_1_INDEX_AM][n], pids1_mult); - decode_push_pids(&st->input->decode, qam16(st->buffer[CENTER_AM + PIDS_1_INDEX_AM][n])); - - st->buffer[CENTER_AM + PIDS_2_INDEX_AM][n] = CMPLXFMUL(st->buffer[CENTER_AM + PIDS_2_INDEX_AM][n], pids2_mult); - decode_push_pids(&st->input->decode, qam16(st->buffer[CENTER_AM + PIDS_2_INDEX_AM][n])); - } - - fcomplex_t pl_mult[PARTITION_WIDTH_AM]; - fcomplex_t pu_mult[PARTITION_WIDTH_AM]; - fcomplex_t s_mult[PARTITION_WIDTH_AM]; - fcomplex_t t_mult[PARTITION_WIDTH_AM]; - - float samperr = 0; - for (int col = 0; col < PARTITION_WIDTH_AM; col++) - { - int train1 = (5 + 11*col) % 32; - int train2 = (21 + 11*col) % 32; - - pl_mult[col] = CMPLXFDIV(CMPLXFMULF(CMPLXF(2.5, -2.5), 2), (CMPLXFADD(st->buffer[CENTER_AM - PRIMARY_INDEX_AM - col][train1], st->buffer[CENTER_AM - PRIMARY_INDEX_AM - col][train2]))); - pu_mult[col] = CMPLXFDIV(CMPLXFMULF(CMPLXF(2.5, -2.5), 2), (CMPLXFADD(st->buffer[CENTER_AM + PRIMARY_INDEX_AM + col][train1], st->buffer[CENTER_AM + PRIMARY_INDEX_AM + col][train2]))); - s_mult[col] = CMPLXFDIV(CMPLXFMULF(CMPLXF(1.5, -0.5), 2), (CMPLXFADD(st->buffer[CENTER_AM + SECONDARY_INDEX_AM + col][train1], st->buffer[CENTER_AM + SECONDARY_INDEX_AM + col][train2]))); - t_mult[col] = CMPLXFDIV(CMPLXFMULF(CMPLXF(-0.5, 0.5), 2), (CMPLXFADD(st->buffer[CENTER_AM + TERTIARY_INDEX_AM + col][train1], st->buffer[CENTER_AM + TERTIARY_INDEX_AM + col][train2]))); - - if (col > 0) - { - samperr += phase_diff(cargf(pl_mult[col]), cargf(pl_mult[col-1])); - samperr += phase_diff(cargf(pu_mult[col]), cargf(pu_mult[col-1])); - } - } - samperr = samperr / (2 * (PARTITION_WIDTH_AM-1)) * FFT_AM / (2 * M_PI); - st->samperr = roundf(samperr); - - for (int n = 0; n < BLKSZ; n++) - { - for (int col = 0; col < PARTITION_WIDTH_AM; col++) - { - st->buffer[CENTER_AM - PRIMARY_INDEX_AM - col][n] = CMPLXFMUL(st->buffer[CENTER_AM - PRIMARY_INDEX_AM - col][n], pl_mult[col]); - st->buffer[CENTER_AM + PRIMARY_INDEX_AM + col][n] = CMPLXFMUL(st->buffer[CENTER_AM + PRIMARY_INDEX_AM + col][n], pu_mult[col]); - st->buffer[CENTER_AM + SECONDARY_INDEX_AM + col][n] = CMPLXFMUL(st->buffer[CENTER_AM + SECONDARY_INDEX_AM + col][n], s_mult[col]); - st->buffer[CENTER_AM + TERTIARY_INDEX_AM + col][n] = CMPLXFMUL(st->buffer[CENTER_AM + TERTIARY_INDEX_AM + col][n], t_mult[col]); - - decode_push_pl_pu_s_t( - &st->input->decode, - qam64(st->buffer[CENTER_AM - PRIMARY_INDEX_AM - col][n]), - qam64(st->buffer[CENTER_AM + PRIMARY_INDEX_AM + col][n]), - qam16(st->buffer[CENTER_AM + SECONDARY_INDEX_AM + col][n]), - qpsk(st->buffer[CENTER_AM + TERTIARY_INDEX_AM + col][n]) - ); - } - } - } -} - -void sync_adjust(sync_t *st, int sample_adj) -{ - int i; - for (i = 0; i < MAX_PARTITIONS * PARTITION_WIDTH + 1; i++) - { - st->costas_phase[LB_START + i] -= sample_adj * (LB_START + i - (FFT_FM / 2)) * 2 * M_PI / FFT_FM; - st->costas_phase[UB_END - i] -= sample_adj * (UB_END - i - (FFT_FM / 2)) * 2 * M_PI / FFT_FM; - } -} - -void sync_push(sync_t *st, fcomplex_t *fftout) -{ - unsigned int i; - - if (st->input->radio->mode == NRSC5_MODE_FM) - { - for (i = 0; i < MAX_PARTITIONS * PARTITION_WIDTH + 1; i++) - { - st->buffer[LB_START + i][st->idx] = fftout[LB_START + i]; - st->buffer[UB_END - i][st->idx] = fftout[UB_END - i]; - } - } - else - { - for (i = CENTER_AM - MAX_INDEX_AM; i <= CENTER_AM + MAX_INDEX_AM; i++) - { - st->buffer[i][st->idx] = fftout[i]; - } - } - - if (++st->idx == BLKSZ) - { - st->idx = 0; - - if (st->input->radio->mode == NRSC5_MODE_FM) - sync_process_fm(st); - else - sync_process_am(st); - } -} - -void sync_reset(sync_t *st) -{ - unsigned int i; - for (i = 0; i < FFT_FM; i++) - { - st->costas_freq[i] = 0; - st->costas_phase[i] = 0; - } - - st->idx = 0; - st->psmi = 1; - st->cfo_wait = 0; - st->offset_history = 0; - st->mer_cnt = 0; - st->error_lb = 0; - st->error_ub = 0; -} - -void sync_init(sync_t *st, input_t *input) -{ - float loop_bw = 0.05, damping = 0.70710678; - float denom = 1 + (2 * damping * loop_bw) + (loop_bw * loop_bw); - st->alpha = (4 * damping * loop_bw) / denom; - st->beta = (4 * loop_bw * loop_bw) / denom; - - st->input = input; - sync_reset(st); -} +/* + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#ifdef _MSC_VER +#define _USE_MATH_DEFINES +#endif + +#include +#include + +#include "defines.h" +#include "input.h" +#include "private.h" +#include "sync.h" + +#define PM_PARTITIONS 10 +#define MAX_PARTITIONS 14 +#define PARTITION_DATA_CARRIERS 18 +#define PARTITION_WIDTH 19 +#define MIDDLE_REF_SC 30 // midpoint of Table 11-3 in 1011s.pdf + +static uint8_t gray4(float f) +{ + if (f < -1) + return 0; + else if (f < 0) + return 2; + else if (f < 1) + return 3; + else + return 1; +} + +static uint8_t gray8(float f) +{ + if (f < -3) + return 0; + else if (f < -2) + return 4; + else if (f < -1) + return 6; + else if (f < 0) + return 2; + else if (f < 1) + return 3; + else if (f < 2) + return 7; + else if (f < 3) + return 5; + else + return 1; +} + +static uint8_t qpsk(fcomplex_t cf) +{ + return (crealf(cf) < 0 ? 0 : 1) | (cimagf(cf) < 0 ? 0 : 2); +} + +static uint8_t qam16(fcomplex_t cf) +{ + return gray4(crealf(cf)) | (gray4(cimagf(cf)) << 2); +} + +static uint8_t qam64(fcomplex_t cf) +{ + return gray8(crealf(cf)) | (gray8(cimagf(cf)) << 3); +} + +static void adjust_ref(sync_t *st, unsigned int ref, int cfo) +{ + unsigned int n; + float cfo_freq = 2 * M_PI * cfo * CP_FM / FFT_FM; + + // sync bits (after DBPSK) + static const signed char sync[] = { + -1, 1, -1, -1, -1, 1, 1 + }; + + for (n = 0; n < BLKSZ; n++) + { + float error = cargf(CMPLXFMUL(CMPLXFMUL(st->buffer[ref][n], st->buffer[ref][n]), cexpf(CMPLXFMULF(CMPLXFNEG(I), 2 * st->costas_phase[ref])))) * 0.5; + + st->phases[ref][n] = st->costas_phase[ref]; + st->buffer[ref][n] = CMPLXFMUL(st->buffer[ref][n], cexpf(CMPLXFMULF(CMPLXFNEG(I), st->costas_phase[ref]))); + + st->costas_freq[ref] += st->beta * error; + if (st->costas_freq[ref] > 0.5) st->costas_freq[ref] = 0.5; + if (st->costas_freq[ref] < -0.5) st->costas_freq[ref] = -0.5; + st->costas_phase[ref] += st->costas_freq[ref] + cfo_freq + (st->alpha * error); + if (st->costas_phase[ref] > M_PI) st->costas_phase[ref] -= 2 * M_PI; + if (st->costas_phase[ref] < -M_PI) st->costas_phase[ref] += 2 * M_PI; + } + + // compare to sync bits + float x = 0; + for (n = 0; n < sizeof(sync); n++) + x += crealf(st->buffer[ref][n]) * sync[n]; + if (x < 0) + { + // adjust phase by pi to compensate + for (n = 0; n < BLKSZ; n++) + { + st->phases[ref][n] += M_PI; + st->buffer[ref][n] = CMPLXFMULF(st->buffer[ref][n], -1); + } + st->costas_phase[ref] += M_PI; + } +} + +static void reset_ref(sync_t *st, unsigned int ref) +{ + for (unsigned int n = 0; n < BLKSZ; n++) + st->buffer[ref][n] = CMPLXFMUL(st->buffer[ref][n], cexpf(CMPLXFMULF(I, st->phases[ref][n]))); +} + +static void decode_dbpsk(const fcomplex_t *buf, unsigned char *data, int size) +{ + unsigned char prev = 0; + + for (int n = 0; n < size; n++) + { + unsigned char bit = crealf(buf[n]) <= 0 ? 0 : 1; + data[n] = bit ^ prev; + prev = bit; + } +} + +static int fuzzy_match(const signed char *needle, unsigned int needle_size, const unsigned char *data, int size) +{ + for (int n = 0; n < size; n++) + { + unsigned int i; + for (i = 0; i < needle_size; i++) + { + // first bit of data may be wrong, so ignore + if ((n + i) % size == 0) continue; + // ignore don't care bits + if (needle[i] < 0) continue; + // test if bit is correct + if (needle[i] != data[(n + i) % size]) + break; + } + if (i == needle_size) + return n; + } + return -1; +} + +static int find_first_block(sync_t *st, unsigned int ref, unsigned int rsid) +{ + signed char needle[] = { + 0, 1, 1, 0, 0, 1, 0, -1, -1, 1, rsid >> 1, rsid & 1, 0, (rsid >> 1) ^ (rsid & 1), 0, -1, 0, 0, 0, 0, -1, 1, 1, 1 + }; + unsigned char data[BLKSZ]; + int n; + + decode_dbpsk(st->buffer[ref], data, BLKSZ); + n = fuzzy_match(needle, sizeof(needle), data, BLKSZ); + if (n == 0) + st->psmi = (data[25] << 5) | (data[26] << 4) | (data[27] << 3) | (data[28] << 2) | (data[29] << 1) | data[30]; + return n; +} + +static int find_ref(sync_t *st, unsigned int ref, unsigned int rsid) +{ + signed char needle[] = { + 0, 1, 1, 0, 0, 1, 0, -1, -1, 1, rsid >> 1, rsid & 1, 0, (rsid >> 1) ^ (rsid & 1), 0, -1, -1, -1, -1, -1, -1, 1, 1, 1 + }; + unsigned char data[BLKSZ]; + + decode_dbpsk(st->buffer[ref], data, BLKSZ); + return fuzzy_match(needle, sizeof(needle), data, BLKSZ); +} + +static int find_block_am(sync_t *st, unsigned int ref) +{ + signed char needle[] = { + 0, 1, 1, 0, 0, 1, 0, -1, -1, 1, -1, -1, -1, -1, 0, -1, -1, -1, -1, -1, -1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1 + }; + unsigned char data[BLKSZ]; + + for (int n = 0; n < BLKSZ; n++) + { + data[n] = cimagf(st->buffer[ref][n]) <= 0 ? 0 : 1; + if ((needle[n] >= 0) && (data[n] != needle[n])) return -1; + } + + // parity checks + if (data[7] ^ data[8]) return -1; + if (data[10] ^ data[11] ^ data[12] ^ data[13]) return -1; + if (data[15] ^ data[16] ^ data[17] ^ data[18] ^ data[19] ^ data[20]) return -1; + if (data[23] ^ data[24] ^ data[25] ^ data[26] ^ data[27] ^ data[28] ^ data[29] ^ data[30] ^ data[31]) return -1; + + return (data[17] << 2) | (data[18] << 1) | data[19]; +} + +static int find_ref_am(sync_t *st, unsigned int ref) +{ + signed char needle[] = { + 0, 1, 1, 0, 0, 1, 0, -1, -1, 1, -1, -1, -1, -1, 0, -1, -1, -1, -1, -1, -1, 1, 1 + }; + unsigned char data[BLKSZ]; + + for (int n = 0; n < BLKSZ; n++) + data[n] = cimagf(st->buffer[ref][n]) <= 0 ? 0 : 1; + + return fuzzy_match(needle, sizeof(needle), data, BLKSZ); +} + +static float calc_smag(sync_t *st, unsigned int ref) +{ + float sum = 0; + // phase was already corrected, so imaginary component is zero + for (int n = 0; n < BLKSZ; n++) + sum += fabsf(crealf(st->buffer[ref][n])); + return sum / BLKSZ; +} + +static void adjust_data(sync_t *st, unsigned int lower, unsigned int upper) +{ + float smag0, smag19; + smag0 = calc_smag(st, lower); + smag19 = calc_smag(st, upper); + + for (int n = 0; n < BLKSZ; n++) + { + fcomplex_t upper_phase = cexpf(CMPLXFMULF(I, st->phases[upper][n])); + fcomplex_t lower_phase = cexpf(CMPLXFMULF(I, st->phases[lower][n])); + + for (int k = 1; k < PARTITION_WIDTH; k++) + { + // average phase difference + fcomplex_t C = CMPLXFDIV(CMPLXF(PARTITION_WIDTH, PARTITION_WIDTH), (CMPLXFADD(CMPLXFMULF(upper_phase, k * smag19), CMPLXFMULF(lower_phase, (PARTITION_WIDTH - k) * smag0)))); + // adjust sample + st->buffer[lower + k][n] = CMPLXFMUL(st->buffer[lower + k][n], C); + } + } +} + +float phase_diff(float a, float b) +{ + float diff = a - b; + while (diff > M_PI / 2) diff -= M_PI; + while (diff < -M_PI / 2) diff += M_PI; + return diff; +} + +void detect_cfo(sync_t *st) +{ + for (int cfo = -2 * PARTITION_WIDTH; cfo < 2 * PARTITION_WIDTH; cfo++) + { + int offset; + int best_offset = -1; + unsigned int best_count = 0; + unsigned int offset_count[BLKSZ]; + + memset(offset_count, 0, BLKSZ * sizeof(unsigned int)); + + for (int i = 0; i <= PM_PARTITIONS; i++) + { + adjust_ref(st, cfo + LB_START + i * PARTITION_WIDTH, cfo); + offset = find_ref(st, cfo + LB_START + i * PARTITION_WIDTH, (MIDDLE_REF_SC-i) & 0x3); + reset_ref(st, cfo + LB_START + i * PARTITION_WIDTH); + if (offset >= 0) + offset_count[offset]++; + + adjust_ref(st, cfo + UB_END - i * PARTITION_WIDTH, cfo); + offset = find_ref(st, cfo + UB_END - i * PARTITION_WIDTH, (MIDDLE_REF_SC-i) & 0x3); + reset_ref(st, cfo + UB_END - i * PARTITION_WIDTH); + if (offset >= 0) + offset_count[offset]++; + } + + for (offset = 0; offset < BLKSZ; offset++) + { + if (offset_count[offset] > best_count) { + best_offset = offset; + best_count = offset_count[offset]; + } + } + + if (best_offset >= 0 && best_count >= 3) + { + // At least three offsets matched, so this is likely the correct CFO. + input_set_skip(st->input, best_offset * FFTCP_FM); + acquire_cfo_adjust(&st->input->acq, cfo); + + log_debug("Block @ %d", best_offset); + + // Wait until the buffers have cleared before measuring again. + st->cfo_wait = 8; + break; + } + } +} + +void sync_process_fm(sync_t *st) +{ + int i, partitions_per_band; + + switch (st->psmi) { + case 2: + partitions_per_band = 11; + break; + case 3: + partitions_per_band = 12; + break; + case 5: + case 6: + case 11: + partitions_per_band = 14; + break; + default: + partitions_per_band = 10; + } + + for (i = 0; i < partitions_per_band * PARTITION_WIDTH + 1; i += PARTITION_WIDTH) + { + adjust_ref(st, LB_START + i, 0); + adjust_ref(st, UB_END - i, 0); + } + + // check if we now have synchronization + if (st->input->sync_state == SYNC_STATE_COARSE) + { + unsigned int good_refs = 0; + for (i = 0; i <= partitions_per_band; i++) + { + if (find_first_block(st, LB_START + i * PARTITION_WIDTH, (MIDDLE_REF_SC-i) & 0x3) == 0) + good_refs++; + if (find_first_block(st, UB_END - i * PARTITION_WIDTH, (MIDDLE_REF_SC-i) & 0x3) == 0) + good_refs++; + } + + if (good_refs >= 4) + { + input_set_sync_state(st->input, SYNC_STATE_FINE); + decode_reset(&st->input->decode); + frame_reset(&st->input->frame); + } + else if (st->cfo_wait == 0) + { + detect_cfo(st); + } + else + { + // Decrease wait counter. + st->cfo_wait--; + } + } + + // if we are still synchronized + if (st->input->sync_state == SYNC_STATE_FINE) + { + float samperr = 0, angle = 0; + float sum_xy = 0, sum_x2 = 0; + for (i = 0; i < partitions_per_band * PARTITION_WIDTH; i += PARTITION_WIDTH) + { + adjust_data(st, LB_START + i, LB_START + i + PARTITION_WIDTH); + adjust_data(st, UB_END - i - PARTITION_WIDTH, UB_END - i); + + samperr += phase_diff(st->phases[LB_START + i][0], st->phases[LB_START + i + PARTITION_WIDTH][0]); + samperr += phase_diff(st->phases[UB_END - i - PARTITION_WIDTH][0], st->phases[UB_END - i][0]); + } + samperr = samperr / (partitions_per_band * 2) * FFT_FM / PARTITION_WIDTH / (2 * M_PI); + + for (i = 0; i < partitions_per_band * PARTITION_WIDTH + 1; i += PARTITION_WIDTH) + { + float x, y; + + x = LB_START + i - (FFT_FM / 2); + y = st->costas_freq[LB_START + i]; + angle += y; + sum_xy += x * y; + sum_x2 += x * x; + + x = UB_END - i - (FFT_FM / 2); + y = st->costas_freq[UB_END - i]; + angle += y; + sum_xy += x * y; + sum_x2 += x * x; + } + samperr -= (sum_xy / sum_x2) * FFT_FM / (2 * M_PI) * ACQUIRE_SYMBOLS; + st->samperr = roundf(samperr); + + angle /= (partitions_per_band + 1) * 2; + st->angle = angle; + + // Calculate modulation error + float error_lb = 0, error_ub = 0; + for (int n = 0; n < BLKSZ; n++) + { + fcomplex_t c, ideal; + for (i = 0; i < partitions_per_band * PARTITION_WIDTH; i += PARTITION_WIDTH) + { + unsigned int j; + for (j = 1; j < PARTITION_WIDTH; j++) + { + c = st->buffer[LB_START + i + j][n]; + ideal = CMPLXF(crealf(c) >= 0 ? 1 : -1, cimagf(c) >= 0 ? 1 : -1); + error_lb += normf(CMPLXFSUB(ideal, c)); + + c = st->buffer[UB_END - i - PARTITION_WIDTH + j][n]; + ideal = CMPLXF(crealf(c) >= 0 ? 1 : -1, cimagf(c) >= 0 ? 1 : -1); + error_ub += normf(CMPLXFSUB(ideal, c)); + } + } + } + + st->error_lb += error_lb; + st->error_ub += error_ub; + + // Display average MER for each sideband + if (++st->mer_cnt == 16) + { + float signal = 2 * BLKSZ * (partitions_per_band * PARTITION_DATA_CARRIERS) * st->mer_cnt; + float mer_db_lb = 10 * log10f(signal / st->error_lb); + float mer_db_ub = 10 * log10f(signal / st->error_ub); + + nrsc5_report_mer(st->input->radio, mer_db_lb, mer_db_ub); + + st->mer_cnt = 0; + st->error_lb = 0; + st->error_ub = 0; + } + + // Soft demod based on MER for each sideband + float mer_lb = 2 * BLKSZ * (partitions_per_band * PARTITION_DATA_CARRIERS) / error_lb; + float mer_ub = 2 * BLKSZ * (partitions_per_band * PARTITION_DATA_CARRIERS) / error_ub; + float mult_lb = fmaxf(fminf(mer_lb * 10, 127), 1); + float mult_ub = fmaxf(fminf(mer_ub * 10, 127), 1); + +#define DEMOD(x) ((x) >= 0 ? 1 : -1) + for (int n = 0; n < BLKSZ; n++) + { + fcomplex_t c; + for (i = LB_START; i < LB_START + (PM_PARTITIONS * PARTITION_WIDTH); i += PARTITION_WIDTH) + { + unsigned int j; + for (j = 1; j < PARTITION_WIDTH; j++) + { + c = st->buffer[i + j][n]; + decode_push_pm(&st->input->decode, DEMOD(crealf(c)) * mult_lb); + decode_push_pm(&st->input->decode, DEMOD(cimagf(c)) * mult_lb); + } + } + for (i = UB_END - (PM_PARTITIONS * PARTITION_WIDTH); i < UB_END; i += PARTITION_WIDTH) + { + unsigned int j; + for (j = 1; j < PARTITION_WIDTH; j++) + { + c = st->buffer[i + j][n]; + decode_push_pm(&st->input->decode, DEMOD(crealf(c)) * mult_ub); + decode_push_pm(&st->input->decode, DEMOD(cimagf(c)) * mult_ub); + } + } + if (st->psmi == 3) { + for (i = LB_START + (PM_PARTITIONS * PARTITION_WIDTH); i < LB_START + (PM_PARTITIONS + 2) * PARTITION_WIDTH; i += PARTITION_WIDTH) + { + unsigned int j; + for (j = 1; j < PARTITION_WIDTH; j++) + { + c = st->buffer[i + j][n]; + decode_push_px1(&st->input->decode, DEMOD(crealf(c)) * mult_lb); + decode_push_px1(&st->input->decode, DEMOD(cimagf(c)) * mult_lb); + } + } + for (i = UB_END - (PM_PARTITIONS + 2) * PARTITION_WIDTH; i < UB_END - (PM_PARTITIONS * PARTITION_WIDTH); i += PARTITION_WIDTH) + { + unsigned int j; + for (j = 1; j < PARTITION_WIDTH; j++) + { + c = st->buffer[i + j][n]; + decode_push_px1(&st->input->decode, DEMOD(crealf(c)) * mult_ub); + decode_push_px1(&st->input->decode, DEMOD(cimagf(c)) * mult_ub); + } + } + } + } + } +} + +void sync_process_am(sync_t *st) +{ + int offset; + + for (int i = REF_INDEX_AM; i <= PIDS_2_INDEX_AM; i++) + { + for (int n = 0; n < BLKSZ; n++) + { + st->buffer[CENTER_AM + i][n] = CMPLXFSUB(st->buffer[CENTER_AM + i][n], conjf(st->buffer[CENTER_AM - i][n])); + } + } + + for (int i = PRIMARY_INDEX_AM; i <= MAX_INDEX_AM; i++) + { + for (int n = 0; n < BLKSZ; n++) + { + st->buffer[CENTER_AM - i][n] = CMPLXFNEG(conjf(st->buffer[CENTER_AM - i][n])); + } + } + + if (st->input->sync_state == SYNC_STATE_COARSE && st->cfo_wait == 0) + { + offset = find_ref_am(st, CENTER_AM + REF_INDEX_AM); + if (offset > 0) + { + input_set_skip(st->input, offset * FFTCP_AM); + log_debug("Block @ %d", offset); + st->cfo_wait = 8; + } + } + else + { + st->cfo_wait--; + } + + if (st->input->sync_state == SYNC_STATE_COARSE) + { + int bc = find_block_am(st, CENTER_AM + REF_INDEX_AM); + + if (bc == -1) + st->offset_history = 0; + else + st->offset_history = (st->offset_history << 4) | bc; + + if ((st->offset_history & 0xffff) == 0x5670) + { + log_debug("Sync!"); + st->input->sync_state = SYNC_STATE_FINE; + decode_reset(&st->input->decode); + frame_reset(&st->input->frame); + st->offset_history = 0; + } + } + + if (st->input->sync_state == SYNC_STATE_FINE) + { + fcomplex_t pids1_mult = CMPLXFDIV(CMPLXFMULF(CMPLXF(1.5, -0.5), 2), (CMPLXFADD(st->buffer[CENTER_AM + PIDS_1_INDEX_AM][8], st->buffer[CENTER_AM + PIDS_1_INDEX_AM][24]))); + fcomplex_t pids2_mult = CMPLXFDIV(CMPLXFMULF(CMPLXF(1.5, -0.5), 2), (CMPLXFADD(st->buffer[CENTER_AM + PIDS_2_INDEX_AM][8], st->buffer[CENTER_AM + PIDS_2_INDEX_AM][24]))); + + for (int n = 0; n < BLKSZ; n++) + { + st->buffer[CENTER_AM + PIDS_1_INDEX_AM][n] = CMPLXFMUL(st->buffer[CENTER_AM + PIDS_1_INDEX_AM][n], pids1_mult); + decode_push_pids(&st->input->decode, qam16(st->buffer[CENTER_AM + PIDS_1_INDEX_AM][n])); + + st->buffer[CENTER_AM + PIDS_2_INDEX_AM][n] = CMPLXFMUL(st->buffer[CENTER_AM + PIDS_2_INDEX_AM][n], pids2_mult); + decode_push_pids(&st->input->decode, qam16(st->buffer[CENTER_AM + PIDS_2_INDEX_AM][n])); + } + + fcomplex_t pl_mult[PARTITION_WIDTH_AM]; + fcomplex_t pu_mult[PARTITION_WIDTH_AM]; + fcomplex_t s_mult[PARTITION_WIDTH_AM]; + fcomplex_t t_mult[PARTITION_WIDTH_AM]; + + float samperr = 0; + for (int col = 0; col < PARTITION_WIDTH_AM; col++) + { + int train1 = (5 + 11*col) % 32; + int train2 = (21 + 11*col) % 32; + + pl_mult[col] = CMPLXFDIV(CMPLXFMULF(CMPLXF(2.5, -2.5), 2), (CMPLXFADD(st->buffer[CENTER_AM - PRIMARY_INDEX_AM - col][train1], st->buffer[CENTER_AM - PRIMARY_INDEX_AM - col][train2]))); + pu_mult[col] = CMPLXFDIV(CMPLXFMULF(CMPLXF(2.5, -2.5), 2), (CMPLXFADD(st->buffer[CENTER_AM + PRIMARY_INDEX_AM + col][train1], st->buffer[CENTER_AM + PRIMARY_INDEX_AM + col][train2]))); + s_mult[col] = CMPLXFDIV(CMPLXFMULF(CMPLXF(1.5, -0.5), 2), (CMPLXFADD(st->buffer[CENTER_AM + SECONDARY_INDEX_AM + col][train1], st->buffer[CENTER_AM + SECONDARY_INDEX_AM + col][train2]))); + t_mult[col] = CMPLXFDIV(CMPLXFMULF(CMPLXF(-0.5, 0.5), 2), (CMPLXFADD(st->buffer[CENTER_AM + TERTIARY_INDEX_AM + col][train1], st->buffer[CENTER_AM + TERTIARY_INDEX_AM + col][train2]))); + + if (col > 0) + { + samperr += phase_diff(cargf(pl_mult[col]), cargf(pl_mult[col-1])); + samperr += phase_diff(cargf(pu_mult[col]), cargf(pu_mult[col-1])); + } + } + samperr = samperr / (2 * (PARTITION_WIDTH_AM-1)) * FFT_AM / (2 * M_PI); + st->samperr = roundf(samperr); + + for (int n = 0; n < BLKSZ; n++) + { + for (int col = 0; col < PARTITION_WIDTH_AM; col++) + { + st->buffer[CENTER_AM - PRIMARY_INDEX_AM - col][n] = CMPLXFMUL(st->buffer[CENTER_AM - PRIMARY_INDEX_AM - col][n], pl_mult[col]); + st->buffer[CENTER_AM + PRIMARY_INDEX_AM + col][n] = CMPLXFMUL(st->buffer[CENTER_AM + PRIMARY_INDEX_AM + col][n], pu_mult[col]); + st->buffer[CENTER_AM + SECONDARY_INDEX_AM + col][n] = CMPLXFMUL(st->buffer[CENTER_AM + SECONDARY_INDEX_AM + col][n], s_mult[col]); + st->buffer[CENTER_AM + TERTIARY_INDEX_AM + col][n] = CMPLXFMUL(st->buffer[CENTER_AM + TERTIARY_INDEX_AM + col][n], t_mult[col]); + + decode_push_pl_pu_s_t( + &st->input->decode, + qam64(st->buffer[CENTER_AM - PRIMARY_INDEX_AM - col][n]), + qam64(st->buffer[CENTER_AM + PRIMARY_INDEX_AM + col][n]), + qam16(st->buffer[CENTER_AM + SECONDARY_INDEX_AM + col][n]), + qpsk(st->buffer[CENTER_AM + TERTIARY_INDEX_AM + col][n]) + ); + } + } + } +} + +void sync_adjust(sync_t *st, int sample_adj) +{ + int i; + for (i = 0; i < MAX_PARTITIONS * PARTITION_WIDTH + 1; i++) + { + st->costas_phase[LB_START + i] -= sample_adj * (LB_START + i - (FFT_FM / 2)) * 2 * M_PI / FFT_FM; + st->costas_phase[UB_END - i] -= sample_adj * (UB_END - i - (FFT_FM / 2)) * 2 * M_PI / FFT_FM; + } +} + +void sync_push(sync_t *st, fcomplex_t *fftout) +{ + unsigned int i; + + if (st->input->radio->mode == NRSC5_MODE_FM) + { + for (i = 0; i < MAX_PARTITIONS * PARTITION_WIDTH + 1; i++) + { + st->buffer[LB_START + i][st->idx] = fftout[LB_START + i]; + st->buffer[UB_END - i][st->idx] = fftout[UB_END - i]; + } + } + else + { + for (i = CENTER_AM - MAX_INDEX_AM; i <= CENTER_AM + MAX_INDEX_AM; i++) + { + st->buffer[i][st->idx] = fftout[i]; + } + } + + if (++st->idx == BLKSZ) + { + st->idx = 0; + + if (st->input->radio->mode == NRSC5_MODE_FM) + sync_process_fm(st); + else + sync_process_am(st); + } +} + +void sync_reset(sync_t *st) +{ + unsigned int i; + for (i = 0; i < FFT_FM; i++) + { + st->costas_freq[i] = 0; + st->costas_phase[i] = 0; + } + + st->idx = 0; + st->psmi = 1; + st->cfo_wait = 0; + st->offset_history = 0; + st->mer_cnt = 0; + st->error_lb = 0; + st->error_ub = 0; +} + +void sync_init(sync_t *st, input_t *input) +{ + float loop_bw = 0.05, damping = 0.70710678; + float denom = 1 + (2 * damping * loop_bw) + (loop_bw * loop_bw); + st->alpha = (4 * damping * loop_bw) / denom; + st->beta = (4 * loop_bw * loop_bw) / denom; + + st->input = input; + sync_reset(st); +} diff --git a/src/hddsp/sync.h b/src/hddsp/sync.h index 6f91fe0..6cd6926 100644 --- a/src/hddsp/sync.h +++ b/src/hddsp/sync.h @@ -1,32 +1,32 @@ -#pragma once - -#include "config.h" - -//#include - -typedef struct -{ - struct input_t *input; - fcomplex_t buffer[FFT_FM][BLKSZ]; - float phases[FFT_FM][BLKSZ]; - unsigned int idx; - int psmi; - int cfo_wait; - unsigned int offset_history; - int samperr; - float angle; - - float alpha; - float beta; - float costas_freq[FFT_FM]; - float costas_phase[FFT_FM]; - - int mer_cnt; - float error_lb; - float error_ub; -} sync_t; - -void sync_adjust(sync_t *st, int sample_adj); -void sync_push(sync_t *st, fcomplex_t *fft); -void sync_reset(sync_t *st); -void sync_init(sync_t *st, struct input_t *input); +#pragma once + +#include "config.h" + +//#include + +typedef struct +{ + struct input_t *input; + fcomplex_t buffer[FFT_FM][BLKSZ]; + float phases[FFT_FM][BLKSZ]; + unsigned int idx; + int psmi; + int cfo_wait; + unsigned int offset_history; + int samperr; + float angle; + + float alpha; + float beta; + float costas_freq[FFT_FM]; + float costas_phase[FFT_FM]; + + int mer_cnt; + float error_lb; + float error_ub; +} sync_t; + +void sync_adjust(sync_t *st, int sample_adj); +void sync_push(sync_t *st, fcomplex_t *fft); +void sync_reset(sync_t *st); +void sync_init(sync_t *st, struct input_t *input); diff --git a/src/hddsp/unicode.c b/src/hddsp/unicode.c index c3e89d4..7547046 100644 --- a/src/hddsp/unicode.c +++ b/src/hddsp/unicode.c @@ -1,78 +1,78 @@ -#include - -#include "unicode.h" - -char *iso_8859_1_to_utf_8(uint8_t *buf, unsigned int len) -{ - unsigned int i, j; - char *out = malloc(len * 2 + 1); - - j = 0; - for (i = 0; i < len; i++) - { - uint8_t ch = buf[i]; - - if (ch < 0x80) - { - out[j++] = ch; - } - else - { - out[j++] = 0xc0 | (ch >> 6); - out[j++] = 0x80 | (ch & 0x3f); - } - } - - out[j] = 0; - return out; -} - -char *ucs_2_to_utf_8(uint8_t *buf, unsigned int len) -{ - unsigned int i = 0, j = 0; - unsigned int big_endian = 0; - char *out = malloc((len / 2) * 3 + 1); - - if (len >= 2) - { - if ((buf[0] == 0xfe) && (buf[1] == 0xff)) - { - big_endian = 1; - i += 2; - } - else if ((buf[0] == 0xff) && (buf[1] == 0xfe)) - { - big_endian = 0; - i += 2; - } - } - - for (; i < len; i += 2) - { - uint16_t ch; - - if (big_endian) - ch = (buf[i] << 8) | buf[i+1]; - else - ch = buf[i] | (buf[i+1] << 8); - - if (ch < 0x80) - { - out[j++] = ch; - } - else if (ch < 0x800) - { - out[j++] = 0xc0 | (ch >> 6); - out[j++] = 0x80 | (ch & 0x3f); - } - else - { - out[j++] = 0xe0 | (ch >> 12); - out[j++] = 0x80 | ((ch >> 6) & 0x3f); - out[j++] = 0x80 | (ch & 0x3f); - } - } - - out[j] = 0; - return out; -} +#include + +#include "unicode.h" + +char *iso_8859_1_to_utf_8(uint8_t *buf, unsigned int len) +{ + unsigned int i, j; + char *out = malloc(len * 2 + 1); + + j = 0; + for (i = 0; i < len; i++) + { + uint8_t ch = buf[i]; + + if (ch < 0x80) + { + out[j++] = ch; + } + else + { + out[j++] = 0xc0 | (ch >> 6); + out[j++] = 0x80 | (ch & 0x3f); + } + } + + out[j] = 0; + return out; +} + +char *ucs_2_to_utf_8(uint8_t *buf, unsigned int len) +{ + unsigned int i = 0, j = 0; + unsigned int big_endian = 0; + char *out = malloc((len / 2) * 3 + 1); + + if (len >= 2) + { + if ((buf[0] == 0xfe) && (buf[1] == 0xff)) + { + big_endian = 1; + i += 2; + } + else if ((buf[0] == 0xff) && (buf[1] == 0xfe)) + { + big_endian = 0; + i += 2; + } + } + + for (; i < len; i += 2) + { + uint16_t ch; + + if (big_endian) + ch = (buf[i] << 8) | buf[i+1]; + else + ch = buf[i] | (buf[i+1] << 8); + + if (ch < 0x80) + { + out[j++] = ch; + } + else if (ch < 0x800) + { + out[j++] = 0xc0 | (ch >> 6); + out[j++] = 0x80 | (ch & 0x3f); + } + else + { + out[j++] = 0xe0 | (ch >> 12); + out[j++] = 0x80 | ((ch >> 6) & 0x3f); + out[j++] = 0x80 | (ch & 0x3f); + } + } + + out[j] = 0; + return out; +} diff --git a/src/hddsp/unicode.h b/src/hddsp/unicode.h index cf11ca8..f0194ee 100644 --- a/src/hddsp/unicode.h +++ b/src/hddsp/unicode.h @@ -1,6 +1,6 @@ -#pragma once - -#include - -char *iso_8859_1_to_utf_8(uint8_t *buf, unsigned int len); -char *ucs_2_to_utf_8(uint8_t *buf, unsigned int len); +#pragma once + +#include + +char *iso_8859_1_to_utf_8(uint8_t *buf, unsigned int len); +char *ucs_2_to_utf_8(uint8_t *buf, unsigned int len); diff --git a/src/hdmuxscanner.cpp b/src/hdmuxscanner.cpp index 86c4524..78f0616 100644 --- a/src/hdmuxscanner.cpp +++ b/src/hdmuxscanner.cpp @@ -1,245 +1,245 @@ -//--------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//--------------------------------------------------------------------------- - -#include "hdmuxscanner.h" - -#include "stdafx.h" - -#include -#include -#include - -#pragma warning(push, 4) - -// hdmuxscanner::SAMPLE_RATE -// -// Fixed device sample rate required for HD Radio -uint32_t const hdmuxscanner::SAMPLE_RATE = 1488375; - -// trim (local) -// -// Trims an std::string instance -inline std::string trim(const std::string& str) -{ - auto left = std::find_if_not(str.begin(), str.end(), isspace); - auto right = std::find_if_not(str.rbegin(), str.rend(), isspace); - return (right.base() <= left) ? std::string() : std::string(left, right.base()); -} - -//--------------------------------------------------------------------------- -// hdmuxscanner Constructor (private) -// -// Arguments: -// -// samplerate - Sample rate of the input data -// frequency - Input data frequency in Hz -// callback - Callback function to invoke on status change - -hdmuxscanner::hdmuxscanner(uint32_t samplerate, uint32_t frequency, callback const& callback) - : m_callback(callback) -{ - assert(samplerate == SAMPLE_RATE); - if (samplerate != SAMPLE_RATE) - throw std::invalid_argument("samplerate"); - - assert((frequency >= 87900000) && (frequency <= 107900000)); - if ((frequency < 87900000) || (frequency > 107900000)) - throw std::invalid_argument("frequency"); - - // Initialize the HD Radio demodulator - nrsc5_open_pipe(&m_nrsc5); - nrsc5_set_mode(m_nrsc5, NRSC5_MODE_FM); - nrsc5_set_callback(m_nrsc5, nrsc5_callback, this); -} - -//--------------------------------------------------------------------------- -// hdmuxscanner Destructor - -hdmuxscanner::~hdmuxscanner() -{ - nrsc5_close(m_nrsc5); // Close NRSC5 - m_nrsc5 = nullptr; // Reset NRSC5 API handle -} - -//--------------------------------------------------------------------------- -// hdmuxscanner::create (static) -// -// Factory method, creates a new hdmuxscanner instance -// -// Arguments: -// -// samplerate - Sample rate of the input data -// frequency - Input data frequency in Hz -// callback - Callback function to invoke on status change - -std::unique_ptr hdmuxscanner::create(uint32_t samplerate, - uint32_t frequency, - callback const& callback) -{ - return std::unique_ptr(new hdmuxscanner(samplerate, frequency, callback)); -} - -//--------------------------------------------------------------------------- -// hdmuxscanner::inputsamples -// -// Pipes input samples into the muliplex scanner -// -// Arguments: -// -// samples - Pointer to the input samples -// length - Length of the input samples, in bytes - -void hdmuxscanner::inputsamples(uint8_t const* samples, size_t length) -{ - // Pipe the samples into NRSC5, it will invoke the necessary callback(s) - nrsc5_pipe_samples_cu8(m_nrsc5, const_cast(samples), static_cast(length)); -} - -//--------------------------------------------------------------------------- -// hdmuxscanner::nrsc5_callback (private, static) -// -// NRSC5 library event callback function -// -// Arguments: -// -// event - NRSC5 event being raised -// arg - Implementation-specific context pointer - -void hdmuxscanner::nrsc5_callback(nrsc5_event_t const* event, void* arg) -{ - assert(arg != nullptr); - reinterpret_cast(arg)->nrsc5_callback(event); -} - -//--------------------------------------------------------------------------- -// hdmuxscanner::nrsc5_callback (private) -// -// NRSC5 library event callback function -// -// Arguments: -// -// event - NRSC5 event being raised - -void hdmuxscanner::nrsc5_callback(nrsc5_event_t const* event) -{ - // NRSC5_EVENT_SYNC - // - // The HD Radio signal has been synchronized (locked) - if (event->event == NRSC5_EVENT_SYNC) - { - - if (m_muxdata.sync == false) - { - - m_muxdata.sync = true; - m_callback(m_muxdata); - } - } - - // NRSC5_EVENT_LOST_SYNC - // - // The HD Radio signal has been lost - else if (event->event == NRSC5_EVENT_LOST_SYNC) - { - - if (m_muxdata.sync == true) - { - - m_muxdata.sync = false; - m_callback(m_muxdata); - } - } - - // NRSC5_EVENT_SIG - // - // Station Information Guide (SIG) records have been decoded - else if (event->event == NRSC5_EVENT_SIG) - { - - bool invokecallback = false; - - nrsc5_sig_service_t* service = event->sig.services; - while (service != nullptr) - { - - // We only care about SERVICE_AUDIO subchannels here - if (service->type == NRSC5_SIG_SERVICE_AUDIO) - { - - assert(service->number > 0); // Should never happen - - // HD Radio subchannels should always be "HDx" - char name[256] = {}; - snprintf(name, std::extent::value, "HD%u", service->number); - std::string servicename(name); - - auto found = - std::find_if(m_muxdata.subchannels.begin(), m_muxdata.subchannels.end(), - [&](auto const& val) -> bool { return val.number == service->number; }); - - // Existing subchannel, check for a change to the name - if (found != m_muxdata.subchannels.end()) - { - - if (servicename != found->name) - { - - found->name = servicename; - invokecallback = true; - } - } - - // New subchannel, add to the vector<> of subchannels - else - { - - m_muxdata.subchannels.push_back({service->number, servicename}); - invokecallback = true; - } - } - - service = service->next; - } - - if (invokecallback) - m_callback(m_muxdata); - } - - // NRSC5_EVENT_SIS - // - // Station Information Service (SIS) data has been decoded - else if (event->event == NRSC5_EVENT_SIS) - { - - std::string name = trim((event->sis.name != nullptr) ? event->sis.name : ""); - if (name != m_muxdata.name) - { - - m_muxdata.name = name; - m_callback(m_muxdata); - } - } -} - -//--------------------------------------------------------------------------- - -#pragma warning(pop) +//--------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//--------------------------------------------------------------------------- + +#include "hdmuxscanner.h" + +#include "stdafx.h" + +#include +#include +#include + +#pragma warning(push, 4) + +// hdmuxscanner::SAMPLE_RATE +// +// Fixed device sample rate required for HD Radio +uint32_t const hdmuxscanner::SAMPLE_RATE = 1488375; + +// trim (local) +// +// Trims an std::string instance +inline std::string trim(const std::string& str) +{ + auto left = std::find_if_not(str.begin(), str.end(), isspace); + auto right = std::find_if_not(str.rbegin(), str.rend(), isspace); + return (right.base() <= left) ? std::string() : std::string(left, right.base()); +} + +//--------------------------------------------------------------------------- +// hdmuxscanner Constructor (private) +// +// Arguments: +// +// samplerate - Sample rate of the input data +// frequency - Input data frequency in Hz +// callback - Callback function to invoke on status change + +hdmuxscanner::hdmuxscanner(uint32_t samplerate, uint32_t frequency, callback const& callback) + : m_callback(callback) +{ + assert(samplerate == SAMPLE_RATE); + if (samplerate != SAMPLE_RATE) + throw std::invalid_argument("samplerate"); + + assert((frequency >= 87900000) && (frequency <= 107900000)); + if ((frequency < 87900000) || (frequency > 107900000)) + throw std::invalid_argument("frequency"); + + // Initialize the HD Radio demodulator + nrsc5_open_pipe(&m_nrsc5); + nrsc5_set_mode(m_nrsc5, NRSC5_MODE_FM); + nrsc5_set_callback(m_nrsc5, nrsc5_callback, this); +} + +//--------------------------------------------------------------------------- +// hdmuxscanner Destructor + +hdmuxscanner::~hdmuxscanner() +{ + nrsc5_close(m_nrsc5); // Close NRSC5 + m_nrsc5 = nullptr; // Reset NRSC5 API handle +} + +//--------------------------------------------------------------------------- +// hdmuxscanner::create (static) +// +// Factory method, creates a new hdmuxscanner instance +// +// Arguments: +// +// samplerate - Sample rate of the input data +// frequency - Input data frequency in Hz +// callback - Callback function to invoke on status change + +std::unique_ptr hdmuxscanner::create(uint32_t samplerate, + uint32_t frequency, + callback const& callback) +{ + return std::unique_ptr(new hdmuxscanner(samplerate, frequency, callback)); +} + +//--------------------------------------------------------------------------- +// hdmuxscanner::inputsamples +// +// Pipes input samples into the muliplex scanner +// +// Arguments: +// +// samples - Pointer to the input samples +// length - Length of the input samples, in bytes + +void hdmuxscanner::inputsamples(uint8_t const* samples, size_t length) +{ + // Pipe the samples into NRSC5, it will invoke the necessary callback(s) + nrsc5_pipe_samples_cu8(m_nrsc5, const_cast(samples), static_cast(length)); +} + +//--------------------------------------------------------------------------- +// hdmuxscanner::nrsc5_callback (private, static) +// +// NRSC5 library event callback function +// +// Arguments: +// +// event - NRSC5 event being raised +// arg - Implementation-specific context pointer + +void hdmuxscanner::nrsc5_callback(nrsc5_event_t const* event, void* arg) +{ + assert(arg != nullptr); + reinterpret_cast(arg)->nrsc5_callback(event); +} + +//--------------------------------------------------------------------------- +// hdmuxscanner::nrsc5_callback (private) +// +// NRSC5 library event callback function +// +// Arguments: +// +// event - NRSC5 event being raised + +void hdmuxscanner::nrsc5_callback(nrsc5_event_t const* event) +{ + // NRSC5_EVENT_SYNC + // + // The HD Radio signal has been synchronized (locked) + if (event->event == NRSC5_EVENT_SYNC) + { + + if (m_muxdata.sync == false) + { + + m_muxdata.sync = true; + m_callback(m_muxdata); + } + } + + // NRSC5_EVENT_LOST_SYNC + // + // The HD Radio signal has been lost + else if (event->event == NRSC5_EVENT_LOST_SYNC) + { + + if (m_muxdata.sync == true) + { + + m_muxdata.sync = false; + m_callback(m_muxdata); + } + } + + // NRSC5_EVENT_SIG + // + // Station Information Guide (SIG) records have been decoded + else if (event->event == NRSC5_EVENT_SIG) + { + + bool invokecallback = false; + + nrsc5_sig_service_t* service = event->sig.services; + while (service != nullptr) + { + + // We only care about SERVICE_AUDIO subchannels here + if (service->type == NRSC5_SIG_SERVICE_AUDIO) + { + + assert(service->number > 0); // Should never happen + + // HD Radio subchannels should always be "HDx" + char name[256] = {}; + snprintf(name, std::extent::value, "HD%u", service->number); + std::string servicename(name); + + auto found = + std::find_if(m_muxdata.subchannels.begin(), m_muxdata.subchannels.end(), + [&](auto const& val) -> bool { return val.number == service->number; }); + + // Existing subchannel, check for a change to the name + if (found != m_muxdata.subchannels.end()) + { + + if (servicename != found->name) + { + + found->name = servicename; + invokecallback = true; + } + } + + // New subchannel, add to the vector<> of subchannels + else + { + + m_muxdata.subchannels.push_back({service->number, servicename}); + invokecallback = true; + } + } + + service = service->next; + } + + if (invokecallback) + m_callback(m_muxdata); + } + + // NRSC5_EVENT_SIS + // + // Station Information Service (SIS) data has been decoded + else if (event->event == NRSC5_EVENT_SIS) + { + + std::string name = trim((event->sis.name != nullptr) ? event->sis.name : ""); + if (name != m_muxdata.name) + { + + m_muxdata.name = name; + m_callback(m_muxdata); + } + } +} + +//--------------------------------------------------------------------------- + +#pragma warning(pop) diff --git a/src/hdmuxscanner.h b/src/hdmuxscanner.h index 8bcc376..ae27b61 100644 --- a/src/hdmuxscanner.h +++ b/src/hdmuxscanner.h @@ -1,101 +1,101 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//----------------------------------------------------------------------------- - -#ifndef __HDMUXSCANNER_H_ -#define __HDMUXSCANNER_H_ -#pragma once - -#include "hddsp/nrsc5.h" -#include "muxscanner.h" -#include "props.h" - -#include -#include - -#pragma warning(push, 4) - -//--------------------------------------------------------------------------- -// Class hdmuxscanner -// -// Implements the multiplex scanner for HD Radio - -class hdmuxscanner : public muxscanner -{ -public: - // Destructor - // - virtual ~hdmuxscanner(); - - //----------------------------------------------------------------------- - // Member Functions - - // create (static) - // - // Factory method, creates a new hdmuxscanner instance - static std::unique_ptr create(uint32_t samplerate, - uint32_t frequency, - callback const& callback); - - // inputsamples - // - // Pipes input samples into the muliplex scanner - void inputsamples(uint8_t const* samples, size_t length) override; - -private: - hdmuxscanner(hdmuxscanner const&) = delete; - hdmuxscanner& operator=(hdmuxscanner const&) = delete; - - // Instance Constructor - // - hdmuxscanner(uint32_t samplerate, uint32_t frequency, callback const& callback); - - // SAMPLE_RATE - // - // Fixed device sample rate required for HD Radio - static uint32_t const SAMPLE_RATE; - - //----------------------------------------------------------------------- - // Private Member Functions - - // nrsc5_callback (static) - // - // NRSC5 library event callback function - static void nrsc5_callback(nrsc5_event_t const* event, void* arg); - - // nrsc5_callback - // - // NRSC5 library event callback function - void nrsc5_callback(nrsc5_event_t const* event); - - //----------------------------------------------------------------------- - // Member Variables - - nrsc5_t* m_nrsc5; // NRSC5 demodulator handle - callback const m_callback; // Callback function - struct multiplex m_muxdata = {}; // Multiplex data -}; - -//----------------------------------------------------------------------------- - -#pragma warning(pop) - -#endif // __HDMUXSCANNER_H_ +//----------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef __HDMUXSCANNER_H_ +#define __HDMUXSCANNER_H_ +#pragma once + +#include "hddsp/nrsc5.h" +#include "muxscanner.h" +#include "props.h" + +#include +#include + +#pragma warning(push, 4) + +//--------------------------------------------------------------------------- +// Class hdmuxscanner +// +// Implements the multiplex scanner for HD Radio + +class hdmuxscanner : public muxscanner +{ +public: + // Destructor + // + virtual ~hdmuxscanner(); + + //----------------------------------------------------------------------- + // Member Functions + + // create (static) + // + // Factory method, creates a new hdmuxscanner instance + static std::unique_ptr create(uint32_t samplerate, + uint32_t frequency, + callback const& callback); + + // inputsamples + // + // Pipes input samples into the muliplex scanner + void inputsamples(uint8_t const* samples, size_t length) override; + +private: + hdmuxscanner(hdmuxscanner const&) = delete; + hdmuxscanner& operator=(hdmuxscanner const&) = delete; + + // Instance Constructor + // + hdmuxscanner(uint32_t samplerate, uint32_t frequency, callback const& callback); + + // SAMPLE_RATE + // + // Fixed device sample rate required for HD Radio + static uint32_t const SAMPLE_RATE; + + //----------------------------------------------------------------------- + // Private Member Functions + + // nrsc5_callback (static) + // + // NRSC5 library event callback function + static void nrsc5_callback(nrsc5_event_t const* event, void* arg); + + // nrsc5_callback + // + // NRSC5 library event callback function + void nrsc5_callback(nrsc5_event_t const* event); + + //----------------------------------------------------------------------- + // Member Variables + + nrsc5_t* m_nrsc5; // NRSC5 demodulator handle + callback const m_callback; // Callback function + struct multiplex m_muxdata = {}; // Multiplex data +}; + +//----------------------------------------------------------------------------- + +#pragma warning(pop) + +#endif // __HDMUXSCANNER_H_ diff --git a/src/hdstream.cpp b/src/hdstream.cpp index 7b89a3f..80d4a86 100644 --- a/src/hdstream.cpp +++ b/src/hdstream.cpp @@ -1,668 +1,668 @@ -//--------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//--------------------------------------------------------------------------- - -#include "hdstream.h" - -#include "align.h" -#include "id3v2tag.h" -#include "stdafx.h" -#include "string_exception.h" - -#include -#include -#include - -// Uncomment to test ID3 tag support -// #define KODI_HAS_ID3 - -#pragma warning(push, 4) - -// hdstream::MAX_PACKET_QUEUE -// -// Maximum number of queued demux packets -size_t const hdstream::MAX_PACKET_QUEUE = 200; // ~2sec analog / ~10sec digital - -// hdstream::SAMPLE_RATE -// -// Fixed device sample rate required for HD Radio -uint32_t const hdstream::SAMPLE_RATE = 1488375; - -// hdstream::STREAM_ID_AUDIO -// -// Stream identifier for the audio output stream -int const hdstream::STREAM_ID_AUDIO = 1; - -// hdstream::STREAM_ID_ID3TAG -// -// Stream identifier for the ID3v2 tag output stream -int const hdstream::STREAM_ID_ID3TAG = 2; - -//--------------------------------------------------------------------------- -// hdstream Constructor (private) -// -// Arguments: -// -// device - RTL-SDR device instance -// tunerprops - Tuner device properties -// channelprops - Channel properties -// hdprops - HD Radio digital signal processor properties -// subchannel - Multiplex subchannel number - -hdstream::hdstream(std::unique_ptr device, - struct tunerprops const& tunerprops, - struct channelprops const& channelprops, - struct hdprops const& hdprops, - uint32_t subchannel) - : m_device(std::move(device)), - m_subchannel((subchannel > 0) ? subchannel : 1), - m_muxname(""), - m_pcmgain(powf(10.0f, hdprops.outputgain / 10.0f)) -{ - // Initialize the RTL-SDR device instance - m_device->set_frequency_correction(tunerprops.freqcorrection + channelprops.freqcorrection); - m_device->set_sample_rate(SAMPLE_RATE); - m_device->set_center_frequency(channelprops.frequency); - - // Adjust the device gain as specified by the channel properties - m_device->set_automatic_gain_control(channelprops.autogain); - if (channelprops.autogain == false) - m_device->set_gain(channelprops.manualgain); - - // Initialize the HD Radio demodulator - nrsc5_open_pipe(&m_nrsc5); - nrsc5_set_mode(m_nrsc5, NRSC5_MODE_FM); - nrsc5_set_callback(m_nrsc5, nrsc5_callback, this); - - // Create a worker thread on which to perform demodulation - scalar_condition started{false}; - m_worker = std::thread(&hdstream::worker, this, std::ref(started)); - started.wait_until_equals(true); -} - -//--------------------------------------------------------------------------- -// hdstream Destructor - -hdstream::~hdstream() -{ - close(); -} - -//--------------------------------------------------------------------------- -// hdstream::canseek -// -// Gets a flag indicating if the stream allows seek operations -// -// Arguments: -// -// NONE - -bool hdstream::canseek(void) const -{ - return false; -} - -//--------------------------------------------------------------------------- -// hdstream::close -// -// Closes the stream -// -// Arguments: -// -// NONE - -void hdstream::close(void) -{ - m_stop = true; // Signal worker thread to stop - if (m_device) - m_device->cancel_async(); // Cancel any async read operations - if (m_worker.joinable()) - m_worker.join(); // Wait for thread - - nrsc5_close(m_nrsc5); // Close NRSC5 - m_nrsc5 = nullptr; // Reset NRSC5 API handle - - m_device.reset(); // Release RTL-SDR device -} - -//--------------------------------------------------------------------------- -// hdstream::create (static) -// -// Factory method, creates a new hdstream instance -// -// Arguments: -// -// device - RTL-SDR device instance -// tunerprops - Tunder device properties -// channelprops - Channel properties -// hdprops - HD Radio digital signal processor properties -// subchannel - Multiplex subchannel number - -std::unique_ptr hdstream::create(std::unique_ptr device, - struct tunerprops const& tunerprops, - struct channelprops const& channelprops, - struct hdprops const& hdprops, - uint32_t subchannel) -{ - return std::unique_ptr( - new hdstream(std::move(device), tunerprops, channelprops, hdprops, subchannel)); -} - -//--------------------------------------------------------------------------- -// hdstream::demuxabort -// -// Aborts the demultiplexer -// -// Arguments: -// -// NONE - -void hdstream::demuxabort(void) -{ -} - -//--------------------------------------------------------------------------- -// hdstream::demuxflush -// -// Flushes the demultiplexer -// -// Arguments: -// -// NONE - -void hdstream::demuxflush(void) -{ -} - -//--------------------------------------------------------------------------- -// hdstream::demuxread -// -// Reads the next packet from the demultiplexer -// -// Arguments: -// -// allocator - DemuxPacket allocation function - -DEMUX_PACKET* hdstream::demuxread(std::function const& allocator) -{ - // Wait up to 100ms for there to be a packet available for processing, don't use - // an unconditional wait here; unlike analog radio there may not be data until - // the digitial signal has been synchronized - std::unique_lock lock(m_queuelock); - if (!m_cv.wait_for(lock, std::chrono::milliseconds(100), - [&]() -> bool { return ((m_queue.size() > 0) || m_stopped.load() == true); })) - return allocator(0); - - // If the worker thread was stopped, check for and re-throw any exception that occurred, - // otherwise assume it was stopped normally and return an empty demultiplexer packet - if (m_stopped.load() == true) - { - - if (m_worker_exception) - std::rethrow_exception(m_worker_exception); - else - return allocator(0); - } - - // Pop off the topmost object from the queue<> and release the lock - std::unique_ptr packet(std::move(m_queue.front())); - m_queue.pop(); - lock.unlock(); - - // The packet queue should never have a null packet in it - assert(packet); - if (!packet) - return allocator(0); - - // Allocate and initialize the DEMUX_PACKET - DEMUX_PACKET* demuxpacket = allocator(packet->size); - if (demuxpacket != nullptr) - { - - demuxpacket->iStreamId = packet->streamid; - demuxpacket->iSize = packet->size; - demuxpacket->duration = packet->duration; - demuxpacket->dts = packet->dts; - demuxpacket->pts = packet->pts; - if (packet->size > 0) - memcpy(demuxpacket->pData, packet->data.get(), packet->size); - } - - return demuxpacket; -} - -//--------------------------------------------------------------------------- -// hdstream::demuxreset -// -// Resets the demultiplexer -// -// Arguments: -// -// NONE - -void hdstream::demuxreset(void) -{ -} - -//--------------------------------------------------------------------------- -// hdstream::devicename -// -// Gets the device name associated with the stream -// -// Arguments: -// -// NONE - -std::string hdstream::devicename(void) const -{ - return std::string(m_device->get_device_name()); -} - -//--------------------------------------------------------------------------- -// hdstream::enumproperties -// -// Enumerates the stream properties -// -// Arguments: -// -// callback - Callback to invoke for each stream - -void hdstream::enumproperties(std::function const& callback) -{ - // AUDIO STREAM - // - streamprops audio = {}; - audio.codec = "pcm_s16le"; - audio.pid = STREAM_ID_AUDIO; - audio.channels = 2; - audio.samplerate = 44100; - audio.bitspersample = 16; - callback(audio); - -#ifdef KODI_HAS_ID3 - // ID3 TAG STREAM - // - streamprops id3 = {}; - id3.codec = "id3"; - id3.pid = STREAM_ID_ID3TAG; - callback(id3); -#endif -} - -//--------------------------------------------------------------------------- -// hdstream::length -// -// Gets the length of the stream; or -1 if stream is real-time -// -// Arguments: -// -// NONE - -long long hdstream::length(void) const -{ - return -1; -} - -//--------------------------------------------------------------------------- -// hdstream::muxname -// -// Gets the mux name associated with the stream -// -// Arguments: -// -// NONE - -std::string hdstream::muxname(void) const -{ - return m_muxname; -} - -//--------------------------------------------------------------------------- -// hdstream::nrsc5_callback (private, static) -// -// NRSC5 library event callback function -// -// Arguments: -// -// event - NRSC5 event being raised -// arg - Implementation-specific context pointer - -void hdstream::nrsc5_callback(nrsc5_event_t const* event, void* arg) -{ - assert(arg != nullptr); - reinterpret_cast(arg)->nrsc5_callback(event); -} - -//--------------------------------------------------------------------------- -// hdstream::nrsc5_callback (private) -// -// NRSC5 library event callback function -// -// Arguments: -// -// event - NRSC5 event being raised - -void hdstream::nrsc5_callback(nrsc5_event_t const* event) -{ - bool queued = false; // Flag if an item was queued - - std::unique_lock lock(m_queuelock); - - // NRSC5_EVENT_AUDIO - // - // A digital stream audio packet has been generated - if (event->event == NRSC5_EVENT_AUDIO) - { - - // Filter out anything other than program zero for now - if (event->audio.program == (m_subchannel - 1)) - { - - // Allocate an initialize a heap buffer and copy the audio data into it - size_t audiosize = event->audio.count * sizeof(int16_t); - std::unique_ptr audiodata(new uint8_t[audiosize]); - - // Apply the specified PCM output gain while copying the audio data into the packet buffer - int16_t* pcmdata = reinterpret_cast(audiodata.get()); - for (size_t index = 0; index < event->audio.count; index++) - pcmdata[index] = static_cast(event->audio.data[index] * m_pcmgain); - - // Generate and queue the audio packet - std::unique_ptr packet = std::make_unique(); - packet->streamid = STREAM_ID_AUDIO; - packet->size = static_cast(audiosize); - packet->duration = (event->audio.count / 2.0 / 44100.0) * STREAM_TIME_BASE; - packet->dts = packet->pts = m_dts; - packet->data = std::move(audiodata); - - m_dts += packet->duration; - - m_queue.emplace(std::move(packet)); - queued = true; - } - } - - // NRSC5_EVENT_BER - // - // Reporting the current bit error rate - else if (event->event == NRSC5_EVENT_BER) - m_ber.store(event->ber.cber); - - // NRSC5_EVENT_MER - // - // Reporting the current modulatation error ratio - else if (event->event == NRSC5_EVENT_MER) - { - - // Store the higher of the two values instead of the mean, some HD radio stations - // are allowed to transmit one sideband at a higher power than the other - m_mer.store(std::max(event->mer.lower, event->mer.upper)); - } - -#ifdef KODI_HAS_ID3 - // NRSC5_EVENT_ID3 - // - // Reporting ID3v2 tag data - else if (event->event == NRSC5_EVENT_ID3) - { - - // Filter out anything other than program zero for now - if (event->id3.program == 0) - { - - size_t tagsize = 0; // Length of the ID3 tag - std::unique_ptr tagdata; // ID3 tag data - - // Check for a cached LOT data item that represents the primary image - if (event->id3.xhdr.mime == NRSC5_MIME_PRIMARY_IMAGE) - { - - auto const& lot = m_lots.find(event->id3.xhdr.lot); - if (lot != m_lots.end()) - { - - // Copy the raw ID3v2 tag data into an id3v2tag instance - std::unique_ptr newtag = - id3v2tag::create(event->id3.raw.data, event->id3.raw.size); - - // Append an APIC cover art frame to the tag with the cached image and remove it from cache - newtag->coverart((lot->second.mime == NRSC5_MIME_JPEG) ? "image/jpeg" : "image/png", - lot->second.data.get(), lot->second.size); - m_lots.erase(lot); - - tagsize = newtag->size(); - tagdata = std::unique_ptr(new uint8_t[tagsize]); - if (!newtag->write(&tagdata[0], tagsize)) - tagsize = 0; - } - } - - // If a custom ID3 tag wasn't generated, use the raw ID3v2 tag data - if (!tagdata || (tagsize == 0)) - { - - tagsize = event->id3.raw.size; - tagdata = std::unique_ptr(new uint8_t[tagsize]); - memcpy(&tagdata[0], event->id3.raw.data, event->id3.raw.size); - } - - // If the ID3 tag data was generated, queue it as a demux packet - if (tagdata && (tagsize > 0)) - { - - std::unique_ptr packet = std::make_unique(); - packet->streamid = 2; - packet->size = static_cast(tagsize); - packet->data = std::move(tagdata); - - m_queue.emplace(std::move(packet)); - queued = true; - } - } - } - - // NRSC5_EVENT_LOT - // - // Reporting LOT item data - else if (event->event == NRSC5_EVENT_LOT) - { - - // Only cache JPEG/PNG images to add to the ID3 tags as album art - if ((event->lot.mime == NRSC5_MIME_JPEG) || (event->lot.mime == NRSC5_MIME_PNG)) - { - - lot_item_t item = {}; - item.mime = event->lot.mime; - item.size = event->lot.size; - item.data = std::unique_ptr(new uint8_t[event->lot.size]); - memcpy(&item.data[0], event->lot.data, event->lot.size); - m_lots[event->lot.lot] = std::move(item); - } - } -#endif - - if (queued) - { - - // If the queue size has exceeded the maximum, the packets aren't - // being processed quickly enough by the demux read function - if (m_queue.size() > MAX_PACKET_QUEUE) - { - - m_queue = demux_queue_t(); // Replace the queue<> - - // Push a DEMUX_SPECIALID_STREAMCHANGE packet into the new queue - std::unique_ptr packet = std::make_unique(); - packet->streamid = DEMUX_SPECIALID_STREAMCHANGE; - m_queue.emplace(std::move(packet)); - - // Reset the decode time stamp - m_dts = STREAM_TIME_BASE; - } - - m_cv.notify_all(); // Notify queue was updated - } -} - -//--------------------------------------------------------------------------- -// hdstream::position -// -// Gets the current position of the stream -// -// Arguments: -// -// NONE - -long long hdstream::position(void) const -{ - return -1; -} - -//--------------------------------------------------------------------------- -// hdstream::read -// -// Reads data from the live stream -// -// Arguments: -// -// buffer - Buffer to receive the live stream data -// count - Size of the destination buffer in bytes - -size_t hdstream::read(uint8_t* /*buffer*/, size_t /*count*/) -{ - return 0; -} - -//--------------------------------------------------------------------------- -// hdstream::realtime -// -// Gets a flag indicating if the stream is real-time -// -// Arguments: -// -// NONE - -bool hdstream::realtime(void) const -{ - return true; -} - -//--------------------------------------------------------------------------- -// hdstream::seek -// -// Sets the stream pointer to a specific position -// -// Arguments: -// -// position - Delta within the stream to seek, relative to whence -// whence - Starting position from which to apply the delta - -long long hdstream::seek(long long /*position*/, int /*whence*/) -{ - return -1; -} - -//--------------------------------------------------------------------------- -// hdstream::servicename -// -// Gets the service name associated with the stream -// -// Arguments: -// -// NONE - -std::string hdstream::servicename(void) const -{ - return std::string("Hybrid Digital (HD) Radio"); -} - -//--------------------------------------------------------------------------- -// hdstream::signalquality -// -// Gets the signal quality as percentages -// -// Arguments: -// -// NONE - -void hdstream::signalquality(int& quality, int& snr) const -{ - // For signal quality, use the NRSC5 Bit Error Rate (BER). A BER of zero - // implies ideal signal quality. I have no idea what the BER tolerance - // is for decoding HD Radio, but from observation a BER with a value - // higher than 0.1 is effectively undecodable so let's call that zero - float ber = m_ber.load(); - ber = std::min(std::max(ber, 0.0f), 0.1f) * 100.0f; - quality = static_cast(((ber - 100.0f) * 100.0f) / -100.0f); - - // For signal-to-noise ratio, use the NRSC5 Modulation Error Ratio (MER). - // A MER of 14 is apparently the ideal for HD Radio, so for now use - // a linear scale from (0...13) to define the SNR percentage - float mer = m_mer.load(); - mer = std::max(std::min(13.0f, mer), 0.0f); - snr = static_cast((mer * 100.0f) / 13.0f); -} - -//--------------------------------------------------------------------------- -// hdstream::worker (private) -// -// Worker thread procedure used to transfer data from the device -// -// Arguments: -// -// started - Condition variable to set when thread has started - -void hdstream::worker(scalar_condition& started) -{ - assert(m_device); - assert(m_nrsc5); - - // read_callback_func (local) - // - // Asynchronous read callback function for the RTL-SDR device - auto read_callback_func = [&](uint8_t const* buffer, size_t count) -> void - { - // Pipe the samples into NRSC5, it will invoke the necessary callback(s) - nrsc5_pipe_samples_cu8(m_nrsc5, const_cast(buffer), static_cast(count)); - }; - - // Begin streaming from the device and inform the caller that the thread is running - m_device->begin_stream(); - started = true; - - // Continuously read data from the device until cancel_async() has been called - // 32 KiB = ~1/100 of a second of data - try - { - m_device->read_async(read_callback_func, 32 KiB); - } - catch (...) - { - m_worker_exception = std::current_exception(); - } - - m_stopped.store(true); // Thread is stopped - m_cv.notify_all(); // Unblock any waiters -} - -//--------------------------------------------------------------------------- - -#pragma warning(pop) +//--------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//--------------------------------------------------------------------------- + +#include "hdstream.h" + +#include "align.h" +#include "id3v2tag.h" +#include "stdafx.h" +#include "string_exception.h" + +#include +#include +#include + +// Uncomment to test ID3 tag support +// #define KODI_HAS_ID3 + +#pragma warning(push, 4) + +// hdstream::MAX_PACKET_QUEUE +// +// Maximum number of queued demux packets +size_t const hdstream::MAX_PACKET_QUEUE = 200; // ~2sec analog / ~10sec digital + +// hdstream::SAMPLE_RATE +// +// Fixed device sample rate required for HD Radio +uint32_t const hdstream::SAMPLE_RATE = 1488375; + +// hdstream::STREAM_ID_AUDIO +// +// Stream identifier for the audio output stream +int const hdstream::STREAM_ID_AUDIO = 1; + +// hdstream::STREAM_ID_ID3TAG +// +// Stream identifier for the ID3v2 tag output stream +int const hdstream::STREAM_ID_ID3TAG = 2; + +//--------------------------------------------------------------------------- +// hdstream Constructor (private) +// +// Arguments: +// +// device - RTL-SDR device instance +// tunerprops - Tuner device properties +// channelprops - Channel properties +// hdprops - HD Radio digital signal processor properties +// subchannel - Multiplex subchannel number + +hdstream::hdstream(std::unique_ptr device, + struct tunerprops const& tunerprops, + struct channelprops const& channelprops, + struct hdprops const& hdprops, + uint32_t subchannel) + : m_device(std::move(device)), + m_subchannel((subchannel > 0) ? subchannel : 1), + m_muxname(""), + m_pcmgain(powf(10.0f, hdprops.outputgain / 10.0f)) +{ + // Initialize the RTL-SDR device instance + m_device->set_frequency_correction(tunerprops.freqcorrection + channelprops.freqcorrection); + m_device->set_sample_rate(SAMPLE_RATE); + m_device->set_center_frequency(channelprops.frequency); + + // Adjust the device gain as specified by the channel properties + m_device->set_automatic_gain_control(channelprops.autogain); + if (channelprops.autogain == false) + m_device->set_gain(channelprops.manualgain); + + // Initialize the HD Radio demodulator + nrsc5_open_pipe(&m_nrsc5); + nrsc5_set_mode(m_nrsc5, NRSC5_MODE_FM); + nrsc5_set_callback(m_nrsc5, nrsc5_callback, this); + + // Create a worker thread on which to perform demodulation + scalar_condition started{false}; + m_worker = std::thread(&hdstream::worker, this, std::ref(started)); + started.wait_until_equals(true); +} + +//--------------------------------------------------------------------------- +// hdstream Destructor + +hdstream::~hdstream() +{ + close(); +} + +//--------------------------------------------------------------------------- +// hdstream::canseek +// +// Gets a flag indicating if the stream allows seek operations +// +// Arguments: +// +// NONE + +bool hdstream::canseek(void) const +{ + return false; +} + +//--------------------------------------------------------------------------- +// hdstream::close +// +// Closes the stream +// +// Arguments: +// +// NONE + +void hdstream::close(void) +{ + m_stop = true; // Signal worker thread to stop + if (m_device) + m_device->cancel_async(); // Cancel any async read operations + if (m_worker.joinable()) + m_worker.join(); // Wait for thread + + nrsc5_close(m_nrsc5); // Close NRSC5 + m_nrsc5 = nullptr; // Reset NRSC5 API handle + + m_device.reset(); // Release RTL-SDR device +} + +//--------------------------------------------------------------------------- +// hdstream::create (static) +// +// Factory method, creates a new hdstream instance +// +// Arguments: +// +// device - RTL-SDR device instance +// tunerprops - Tunder device properties +// channelprops - Channel properties +// hdprops - HD Radio digital signal processor properties +// subchannel - Multiplex subchannel number + +std::unique_ptr hdstream::create(std::unique_ptr device, + struct tunerprops const& tunerprops, + struct channelprops const& channelprops, + struct hdprops const& hdprops, + uint32_t subchannel) +{ + return std::unique_ptr( + new hdstream(std::move(device), tunerprops, channelprops, hdprops, subchannel)); +} + +//--------------------------------------------------------------------------- +// hdstream::demuxabort +// +// Aborts the demultiplexer +// +// Arguments: +// +// NONE + +void hdstream::demuxabort(void) +{ +} + +//--------------------------------------------------------------------------- +// hdstream::demuxflush +// +// Flushes the demultiplexer +// +// Arguments: +// +// NONE + +void hdstream::demuxflush(void) +{ +} + +//--------------------------------------------------------------------------- +// hdstream::demuxread +// +// Reads the next packet from the demultiplexer +// +// Arguments: +// +// allocator - DemuxPacket allocation function + +DEMUX_PACKET* hdstream::demuxread(std::function const& allocator) +{ + // Wait up to 100ms for there to be a packet available for processing, don't use + // an unconditional wait here; unlike analog radio there may not be data until + // the digitial signal has been synchronized + std::unique_lock lock(m_queuelock); + if (!m_cv.wait_for(lock, std::chrono::milliseconds(100), + [&]() -> bool { return ((m_queue.size() > 0) || m_stopped.load() == true); })) + return allocator(0); + + // If the worker thread was stopped, check for and re-throw any exception that occurred, + // otherwise assume it was stopped normally and return an empty demultiplexer packet + if (m_stopped.load() == true) + { + + if (m_worker_exception) + std::rethrow_exception(m_worker_exception); + else + return allocator(0); + } + + // Pop off the topmost object from the queue<> and release the lock + std::unique_ptr packet(std::move(m_queue.front())); + m_queue.pop(); + lock.unlock(); + + // The packet queue should never have a null packet in it + assert(packet); + if (!packet) + return allocator(0); + + // Allocate and initialize the DEMUX_PACKET + DEMUX_PACKET* demuxpacket = allocator(packet->size); + if (demuxpacket != nullptr) + { + + demuxpacket->iStreamId = packet->streamid; + demuxpacket->iSize = packet->size; + demuxpacket->duration = packet->duration; + demuxpacket->dts = packet->dts; + demuxpacket->pts = packet->pts; + if (packet->size > 0) + memcpy(demuxpacket->pData, packet->data.get(), packet->size); + } + + return demuxpacket; +} + +//--------------------------------------------------------------------------- +// hdstream::demuxreset +// +// Resets the demultiplexer +// +// Arguments: +// +// NONE + +void hdstream::demuxreset(void) +{ +} + +//--------------------------------------------------------------------------- +// hdstream::devicename +// +// Gets the device name associated with the stream +// +// Arguments: +// +// NONE + +std::string hdstream::devicename(void) const +{ + return std::string(m_device->get_device_name()); +} + +//--------------------------------------------------------------------------- +// hdstream::enumproperties +// +// Enumerates the stream properties +// +// Arguments: +// +// callback - Callback to invoke for each stream + +void hdstream::enumproperties(std::function const& callback) +{ + // AUDIO STREAM + // + streamprops audio = {}; + audio.codec = "pcm_s16le"; + audio.pid = STREAM_ID_AUDIO; + audio.channels = 2; + audio.samplerate = 44100; + audio.bitspersample = 16; + callback(audio); + +#ifdef KODI_HAS_ID3 + // ID3 TAG STREAM + // + streamprops id3 = {}; + id3.codec = "id3"; + id3.pid = STREAM_ID_ID3TAG; + callback(id3); +#endif +} + +//--------------------------------------------------------------------------- +// hdstream::length +// +// Gets the length of the stream; or -1 if stream is real-time +// +// Arguments: +// +// NONE + +long long hdstream::length(void) const +{ + return -1; +} + +//--------------------------------------------------------------------------- +// hdstream::muxname +// +// Gets the mux name associated with the stream +// +// Arguments: +// +// NONE + +std::string hdstream::muxname(void) const +{ + return m_muxname; +} + +//--------------------------------------------------------------------------- +// hdstream::nrsc5_callback (private, static) +// +// NRSC5 library event callback function +// +// Arguments: +// +// event - NRSC5 event being raised +// arg - Implementation-specific context pointer + +void hdstream::nrsc5_callback(nrsc5_event_t const* event, void* arg) +{ + assert(arg != nullptr); + reinterpret_cast(arg)->nrsc5_callback(event); +} + +//--------------------------------------------------------------------------- +// hdstream::nrsc5_callback (private) +// +// NRSC5 library event callback function +// +// Arguments: +// +// event - NRSC5 event being raised + +void hdstream::nrsc5_callback(nrsc5_event_t const* event) +{ + bool queued = false; // Flag if an item was queued + + std::unique_lock lock(m_queuelock); + + // NRSC5_EVENT_AUDIO + // + // A digital stream audio packet has been generated + if (event->event == NRSC5_EVENT_AUDIO) + { + + // Filter out anything other than program zero for now + if (event->audio.program == (m_subchannel - 1)) + { + + // Allocate an initialize a heap buffer and copy the audio data into it + size_t audiosize = event->audio.count * sizeof(int16_t); + std::unique_ptr audiodata(new uint8_t[audiosize]); + + // Apply the specified PCM output gain while copying the audio data into the packet buffer + int16_t* pcmdata = reinterpret_cast(audiodata.get()); + for (size_t index = 0; index < event->audio.count; index++) + pcmdata[index] = static_cast(event->audio.data[index] * m_pcmgain); + + // Generate and queue the audio packet + std::unique_ptr packet = std::make_unique(); + packet->streamid = STREAM_ID_AUDIO; + packet->size = static_cast(audiosize); + packet->duration = (event->audio.count / 2.0 / 44100.0) * STREAM_TIME_BASE; + packet->dts = packet->pts = m_dts; + packet->data = std::move(audiodata); + + m_dts += packet->duration; + + m_queue.emplace(std::move(packet)); + queued = true; + } + } + + // NRSC5_EVENT_BER + // + // Reporting the current bit error rate + else if (event->event == NRSC5_EVENT_BER) + m_ber.store(event->ber.cber); + + // NRSC5_EVENT_MER + // + // Reporting the current modulatation error ratio + else if (event->event == NRSC5_EVENT_MER) + { + + // Store the higher of the two values instead of the mean, some HD radio stations + // are allowed to transmit one sideband at a higher power than the other + m_mer.store(std::max(event->mer.lower, event->mer.upper)); + } + +#ifdef KODI_HAS_ID3 + // NRSC5_EVENT_ID3 + // + // Reporting ID3v2 tag data + else if (event->event == NRSC5_EVENT_ID3) + { + + // Filter out anything other than program zero for now + if (event->id3.program == 0) + { + + size_t tagsize = 0; // Length of the ID3 tag + std::unique_ptr tagdata; // ID3 tag data + + // Check for a cached LOT data item that represents the primary image + if (event->id3.xhdr.mime == NRSC5_MIME_PRIMARY_IMAGE) + { + + auto const& lot = m_lots.find(event->id3.xhdr.lot); + if (lot != m_lots.end()) + { + + // Copy the raw ID3v2 tag data into an id3v2tag instance + std::unique_ptr newtag = + id3v2tag::create(event->id3.raw.data, event->id3.raw.size); + + // Append an APIC cover art frame to the tag with the cached image and remove it from cache + newtag->coverart((lot->second.mime == NRSC5_MIME_JPEG) ? "image/jpeg" : "image/png", + lot->second.data.get(), lot->second.size); + m_lots.erase(lot); + + tagsize = newtag->size(); + tagdata = std::unique_ptr(new uint8_t[tagsize]); + if (!newtag->write(&tagdata[0], tagsize)) + tagsize = 0; + } + } + + // If a custom ID3 tag wasn't generated, use the raw ID3v2 tag data + if (!tagdata || (tagsize == 0)) + { + + tagsize = event->id3.raw.size; + tagdata = std::unique_ptr(new uint8_t[tagsize]); + memcpy(&tagdata[0], event->id3.raw.data, event->id3.raw.size); + } + + // If the ID3 tag data was generated, queue it as a demux packet + if (tagdata && (tagsize > 0)) + { + + std::unique_ptr packet = std::make_unique(); + packet->streamid = 2; + packet->size = static_cast(tagsize); + packet->data = std::move(tagdata); + + m_queue.emplace(std::move(packet)); + queued = true; + } + } + } + + // NRSC5_EVENT_LOT + // + // Reporting LOT item data + else if (event->event == NRSC5_EVENT_LOT) + { + + // Only cache JPEG/PNG images to add to the ID3 tags as album art + if ((event->lot.mime == NRSC5_MIME_JPEG) || (event->lot.mime == NRSC5_MIME_PNG)) + { + + lot_item_t item = {}; + item.mime = event->lot.mime; + item.size = event->lot.size; + item.data = std::unique_ptr(new uint8_t[event->lot.size]); + memcpy(&item.data[0], event->lot.data, event->lot.size); + m_lots[event->lot.lot] = std::move(item); + } + } +#endif + + if (queued) + { + + // If the queue size has exceeded the maximum, the packets aren't + // being processed quickly enough by the demux read function + if (m_queue.size() > MAX_PACKET_QUEUE) + { + + m_queue = demux_queue_t(); // Replace the queue<> + + // Push a DEMUX_SPECIALID_STREAMCHANGE packet into the new queue + std::unique_ptr packet = std::make_unique(); + packet->streamid = DEMUX_SPECIALID_STREAMCHANGE; + m_queue.emplace(std::move(packet)); + + // Reset the decode time stamp + m_dts = STREAM_TIME_BASE; + } + + m_cv.notify_all(); // Notify queue was updated + } +} + +//--------------------------------------------------------------------------- +// hdstream::position +// +// Gets the current position of the stream +// +// Arguments: +// +// NONE + +long long hdstream::position(void) const +{ + return -1; +} + +//--------------------------------------------------------------------------- +// hdstream::read +// +// Reads data from the live stream +// +// Arguments: +// +// buffer - Buffer to receive the live stream data +// count - Size of the destination buffer in bytes + +size_t hdstream::read(uint8_t* /*buffer*/, size_t /*count*/) +{ + return 0; +} + +//--------------------------------------------------------------------------- +// hdstream::realtime +// +// Gets a flag indicating if the stream is real-time +// +// Arguments: +// +// NONE + +bool hdstream::realtime(void) const +{ + return true; +} + +//--------------------------------------------------------------------------- +// hdstream::seek +// +// Sets the stream pointer to a specific position +// +// Arguments: +// +// position - Delta within the stream to seek, relative to whence +// whence - Starting position from which to apply the delta + +long long hdstream::seek(long long /*position*/, int /*whence*/) +{ + return -1; +} + +//--------------------------------------------------------------------------- +// hdstream::servicename +// +// Gets the service name associated with the stream +// +// Arguments: +// +// NONE + +std::string hdstream::servicename(void) const +{ + return std::string("Hybrid Digital (HD) Radio"); +} + +//--------------------------------------------------------------------------- +// hdstream::signalquality +// +// Gets the signal quality as percentages +// +// Arguments: +// +// NONE + +void hdstream::signalquality(int& quality, int& snr) const +{ + // For signal quality, use the NRSC5 Bit Error Rate (BER). A BER of zero + // implies ideal signal quality. I have no idea what the BER tolerance + // is for decoding HD Radio, but from observation a BER with a value + // higher than 0.1 is effectively undecodable so let's call that zero + float ber = m_ber.load(); + ber = std::min(std::max(ber, 0.0f), 0.1f) * 100.0f; + quality = static_cast(((ber - 100.0f) * 100.0f) / -100.0f); + + // For signal-to-noise ratio, use the NRSC5 Modulation Error Ratio (MER). + // A MER of 14 is apparently the ideal for HD Radio, so for now use + // a linear scale from (0...13) to define the SNR percentage + float mer = m_mer.load(); + mer = std::max(std::min(13.0f, mer), 0.0f); + snr = static_cast((mer * 100.0f) / 13.0f); +} + +//--------------------------------------------------------------------------- +// hdstream::worker (private) +// +// Worker thread procedure used to transfer data from the device +// +// Arguments: +// +// started - Condition variable to set when thread has started + +void hdstream::worker(scalar_condition& started) +{ + assert(m_device); + assert(m_nrsc5); + + // read_callback_func (local) + // + // Asynchronous read callback function for the RTL-SDR device + auto read_callback_func = [&](uint8_t const* buffer, size_t count) -> void + { + // Pipe the samples into NRSC5, it will invoke the necessary callback(s) + nrsc5_pipe_samples_cu8(m_nrsc5, const_cast(buffer), static_cast(count)); + }; + + // Begin streaming from the device and inform the caller that the thread is running + m_device->begin_stream(); + started = true; + + // Continuously read data from the device until cancel_async() has been called + // 32 KiB = ~1/100 of a second of data + try + { + m_device->read_async(read_callback_func, 32 KiB); + } + catch (...) + { + m_worker_exception = std::current_exception(); + } + + m_stopped.store(true); // Thread is stopped + m_cv.notify_all(); // Unblock any waiters +} + +//--------------------------------------------------------------------------- + +#pragma warning(pop) diff --git a/src/hdstream.h b/src/hdstream.h index de24354..41afad6 100644 --- a/src/hdstream.h +++ b/src/hdstream.h @@ -1,265 +1,265 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//----------------------------------------------------------------------------- - -#ifndef __HDSTREAM_H_ -#define __HDSTREAM_H_ -#pragma once - -#include "hddsp/nrsc5.h" -#include "props.h" -#include "pvrstream.h" -#include "rtldevice.h" -#include "scalar_condition.h" - -#include -#include -#include -#include -#include -#include -#include - -#pragma warning(push, 4) - -//--------------------------------------------------------------------------- -// Class hdstream -// -// Implements an HD Radio stream - -class hdstream : public pvrstream -{ -public: - // Destructor - // - virtual ~hdstream(); - - //----------------------------------------------------------------------- - // Member Functions - - // canseek - // - // Flag indicating if the stream allows seek operations - bool canseek(void) const override; - - // close - // - // Closes the stream - void close(void) override; - - // create (static) - // - // Factory method, creates a new hdstream instance - static std::unique_ptr create(std::unique_ptr device, - struct tunerprops const& tunerprops, - struct channelprops const& channelprops, - struct hdprops const& hdprops, - uint32_t subchannel); - - // demuxabort - // - // Aborts the demultiplexer - void demuxabort(void) override; - - // demuxflush - // - // Flushes the demultiplexer - void demuxflush(void) override; - - // demuxread - // - // Reads the next packet from the demultiplexer - DEMUX_PACKET* demuxread(std::function const& allocator) override; - - // demuxreset - // - // Resets the demultiplexer - void demuxreset(void) override; - - // devicename - // - // Gets the device name associated with the stream - std::string devicename(void) const override; - - // enumproperties - // - // Enumerates the stream properties - void enumproperties( - std::function const& callback) override; - - // length - // - // Gets the length of the stream - long long length(void) const override; - - // muxname - // - // Gets the mux name associated with the stream - std::string muxname(void) const override; - - // position - // - // Gets the current position of the stream - long long position(void) const override; - - // read - // - // Reads available data from the stream - size_t read(uint8_t* buffer, size_t count) override; - - // realtime - // - // Gets a flag indicating if the stream is real-time - bool realtime(void) const override; - - // seek - // - // Sets the stream pointer to a specific position - long long seek(long long position, int whence) override; - - // servicename - // - // Gets the service name associated with the stream - std::string servicename(void) const override; - - // signalquality - // - // Gets the signal quality as percentages - void signalquality(int& quality, int& snr) const override; - -private: - hdstream(hdstream const&) = delete; - hdstream& operator=(hdstream const&) = delete; - - // MAX_PACKET_QUEUE - // - // Maximum number of queued demux packets - static size_t const MAX_PACKET_QUEUE; - - // SAMPLE_RATE - // - // Fixed device sample rate required for HD Radio - static uint32_t const SAMPLE_RATE; - - // STREAM_ID_AUDIO - // - // Stream identifier for the audio output stream - static int const STREAM_ID_AUDIO; - - // STREAM_ID_ID3TAG - // - // Stream identifier for the ID3v2 tag output stream - static int const STREAM_ID_ID3TAG; - - // Instance Constructor - // - hdstream(std::unique_ptr device, - struct tunerprops const& tunerprops, - struct channelprops const& channelprops, - struct hdprops const& hdprops, - uint32_t subchannel); - - //----------------------------------------------------------------------- - // Private Type Declarations - - // demux_packet_t - // - // Defines the conents of a queued demux packet - struct demux_packet_t - { - - int streamid = 0; - int size = 0; - double duration = 0; - double dts = 0; - double pts = 0; - std::unique_ptr data; - }; - - // demux_queue_t - // - // Defines the type of the demux queue - using demux_queue_t = std::queue>; - - // lot_item_t - // - // Defines the contents of a mapped LOT item - struct lot_item_t - { - - uint32_t mime; - size_t size; - std::unique_ptr data; - }; - - // lot_map_t - // - // Defines the LOT item cache - using lot_map_t = std::map; - - //----------------------------------------------------------------------- - // Private Member Functions - - // nrsc5_callback (static) - // - // NRSC5 library event callback function - static void nrsc5_callback(nrsc5_event_t const* event, void* arg); - - // nrsc5_callback - // - // NRSC5 library event callback function - void nrsc5_callback(nrsc5_event_t const* event); - - // worker - // - // Worker thread procedure used to transfer data from the device - void worker(scalar_condition& started); - - //----------------------------------------------------------------------- - // Member Variables - - std::unique_ptr m_device; // RTL-SDR device instance - nrsc5_t* m_nrsc5; // NRSC5 demodulator handle - - uint32_t const m_subchannel; // Multiplex subchannel number - std::string m_muxname; // Generated mux name - float const m_pcmgain; // Output gain - double m_dts{STREAM_TIME_BASE}; // Current decode time stamp - std::atomic m_mer{0}; // Current modulation error ratio - std::atomic m_ber{0}; // Current bit erorr rate - lot_map_t m_lots; // Cached LOT item data - - // STREAM CONTROL - // - demux_queue_t m_queue; // queue<> of demux objects - mutable std::mutex m_queuelock; // Synchronization object - std::condition_variable m_cv; // Transfer event condvar - std::thread m_worker; // Data transfer thread - std::exception_ptr m_worker_exception; // Exception on worker thread - scalar_condition m_stop{false}; // Condition to stop data transfer - std::atomic m_stopped{false}; // Data transfer stopped flag -}; - -//----------------------------------------------------------------------------- - -#pragma warning(pop) - -#endif // __HDSTREAM_H_ +//----------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef __HDSTREAM_H_ +#define __HDSTREAM_H_ +#pragma once + +#include "hddsp/nrsc5.h" +#include "props.h" +#include "pvrstream.h" +#include "rtldevice.h" +#include "scalar_condition.h" + +#include +#include +#include +#include +#include +#include +#include + +#pragma warning(push, 4) + +//--------------------------------------------------------------------------- +// Class hdstream +// +// Implements an HD Radio stream + +class hdstream : public pvrstream +{ +public: + // Destructor + // + virtual ~hdstream(); + + //----------------------------------------------------------------------- + // Member Functions + + // canseek + // + // Flag indicating if the stream allows seek operations + bool canseek(void) const override; + + // close + // + // Closes the stream + void close(void) override; + + // create (static) + // + // Factory method, creates a new hdstream instance + static std::unique_ptr create(std::unique_ptr device, + struct tunerprops const& tunerprops, + struct channelprops const& channelprops, + struct hdprops const& hdprops, + uint32_t subchannel); + + // demuxabort + // + // Aborts the demultiplexer + void demuxabort(void) override; + + // demuxflush + // + // Flushes the demultiplexer + void demuxflush(void) override; + + // demuxread + // + // Reads the next packet from the demultiplexer + DEMUX_PACKET* demuxread(std::function const& allocator) override; + + // demuxreset + // + // Resets the demultiplexer + void demuxreset(void) override; + + // devicename + // + // Gets the device name associated with the stream + std::string devicename(void) const override; + + // enumproperties + // + // Enumerates the stream properties + void enumproperties( + std::function const& callback) override; + + // length + // + // Gets the length of the stream + long long length(void) const override; + + // muxname + // + // Gets the mux name associated with the stream + std::string muxname(void) const override; + + // position + // + // Gets the current position of the stream + long long position(void) const override; + + // read + // + // Reads available data from the stream + size_t read(uint8_t* buffer, size_t count) override; + + // realtime + // + // Gets a flag indicating if the stream is real-time + bool realtime(void) const override; + + // seek + // + // Sets the stream pointer to a specific position + long long seek(long long position, int whence) override; + + // servicename + // + // Gets the service name associated with the stream + std::string servicename(void) const override; + + // signalquality + // + // Gets the signal quality as percentages + void signalquality(int& quality, int& snr) const override; + +private: + hdstream(hdstream const&) = delete; + hdstream& operator=(hdstream const&) = delete; + + // MAX_PACKET_QUEUE + // + // Maximum number of queued demux packets + static size_t const MAX_PACKET_QUEUE; + + // SAMPLE_RATE + // + // Fixed device sample rate required for HD Radio + static uint32_t const SAMPLE_RATE; + + // STREAM_ID_AUDIO + // + // Stream identifier for the audio output stream + static int const STREAM_ID_AUDIO; + + // STREAM_ID_ID3TAG + // + // Stream identifier for the ID3v2 tag output stream + static int const STREAM_ID_ID3TAG; + + // Instance Constructor + // + hdstream(std::unique_ptr device, + struct tunerprops const& tunerprops, + struct channelprops const& channelprops, + struct hdprops const& hdprops, + uint32_t subchannel); + + //----------------------------------------------------------------------- + // Private Type Declarations + + // demux_packet_t + // + // Defines the conents of a queued demux packet + struct demux_packet_t + { + + int streamid = 0; + int size = 0; + double duration = 0; + double dts = 0; + double pts = 0; + std::unique_ptr data; + }; + + // demux_queue_t + // + // Defines the type of the demux queue + using demux_queue_t = std::queue>; + + // lot_item_t + // + // Defines the contents of a mapped LOT item + struct lot_item_t + { + + uint32_t mime; + size_t size; + std::unique_ptr data; + }; + + // lot_map_t + // + // Defines the LOT item cache + using lot_map_t = std::map; + + //----------------------------------------------------------------------- + // Private Member Functions + + // nrsc5_callback (static) + // + // NRSC5 library event callback function + static void nrsc5_callback(nrsc5_event_t const* event, void* arg); + + // nrsc5_callback + // + // NRSC5 library event callback function + void nrsc5_callback(nrsc5_event_t const* event); + + // worker + // + // Worker thread procedure used to transfer data from the device + void worker(scalar_condition& started); + + //----------------------------------------------------------------------- + // Member Variables + + std::unique_ptr m_device; // RTL-SDR device instance + nrsc5_t* m_nrsc5; // NRSC5 demodulator handle + + uint32_t const m_subchannel; // Multiplex subchannel number + std::string m_muxname; // Generated mux name + float const m_pcmgain; // Output gain + double m_dts{STREAM_TIME_BASE}; // Current decode time stamp + std::atomic m_mer{0}; // Current modulation error ratio + std::atomic m_ber{0}; // Current bit erorr rate + lot_map_t m_lots; // Cached LOT item data + + // STREAM CONTROL + // + demux_queue_t m_queue; // queue<> of demux objects + mutable std::mutex m_queuelock; // Synchronization object + std::condition_variable m_cv; // Transfer event condvar + std::thread m_worker; // Data transfer thread + std::exception_ptr m_worker_exception; // Exception on worker thread + scalar_condition m_stop{false}; // Condition to stop data transfer + std::atomic m_stopped{false}; // Data transfer stopped flag +}; + +//----------------------------------------------------------------------------- + +#pragma warning(pop) + +#endif // __HDSTREAM_H_ diff --git a/src/id3v1tag.cpp b/src/id3v1tag.cpp index 37c0ae2..c0fc395 100644 --- a/src/id3v1tag.cpp +++ b/src/id3v1tag.cpp @@ -1,416 +1,416 @@ -//--------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//--------------------------------------------------------------------------- - -#include "id3v1tag.h" - -#include "stdafx.h" -#include "string_exception.h" - -#include -#include -#include - -#pragma warning(push, 4) - -// g_genres -// -// List of ID3v1 genre strings. Note that this list is not exhaustive and -// only implements the original 79 genres specified by ID3v1: -// -// https://www.oreilly.com/library/view/mp3-the-definitive/1565926617/apa.html -static char const* const g_genres[] = { - - "Blues", - "Classic Rock", - "Country", - "Dance", - "Disco", - "Funk", - "Grunge", - "Hip-Hop", - "Jazz", - "Metal", - "New Age", - "Oldies", - "Other", - "Pop", - "R&B", - "Rap", - "Reggae", - "Rock", - "Techno", - "Industrial", - "Alternative", - "Ska", - "Death Metal", - "Pranks", - "Soundtrack", - "Euro-Techno", - "Ambient", - "Trip-Hop", - "Vocal", - "Jazz+Funk", - "Fusion", - "Trance", - "Classical", - "Instrumental", - "Acid", - "House", - "Game", - "Sound Clip", - "Gospel", - "Noise", - "AlternRock", - "Bass", - "Soul", - "Punk", - "Space", - "Meditative", - "Instrumental Pop", - "Instrumental Rock", - "Ethnic", - "Gothic", - "Darkwave", - "Techno-Industrial", - "Electronic", - "Pop-Folk", - "Eurodance", - "Dream", - "Southern Rock", - "Comedy", - "Cult", - "Gangsta", - "Top 40", - "Christian Rap", - "Pop/Funk", - "Jungle", - "Native American", - "Cabaret", - "New Wave", - "Psychedelic", - "Rave", - "Showtunes", - "Trailer", - "Lo-Fi", - "Tribal", - "Acid Punk", - "Acid Jazz", - "Polka", - "Retro", - "Musical", - "Rock & Roll", - "Hard Rock", -}; - -// g_numgenres -// -// Indicates the number of genres in g_genres -static int const g_numgenres = sizeof(g_genres) / sizeof(g_genres[0]); - -// id3v1tag::UNSPECIFIED_GENRE -// -// Flag indicating that a genre is unspecified -uint8_t const id3v1tag::UNSPECIFIED_GENRE = 255; - -//--------------------------------------------------------------------------- -// id3v1tag Constructor (private) -// -// Arguments: -// -// NONE - -id3v1tag::id3v1tag() -{ - memcpy(m_tag.id, "TAG", 3); - m_tag.genre = UNSPECIFIED_GENRE; -} - -//--------------------------------------------------------------------------- -// id3v1tag Constructor (private) -// -// Arguments: -// -// data - Pointer to existing ID3v1 tag data -// length - Length of existing ID3v1 tag data - -id3v1tag::id3v1tag(uint8_t const* data, size_t length) -{ - if (data == nullptr) - throw std::invalid_argument("data"); - if (length != sizeof(id3v1_tag_t)) - throw std::invalid_argument("length"); - - if (memcmp(data, "TAG", 3) != 0) - throw string_exception("invalid ID3v1 header"); - - memcpy(&m_tag, data, length); // Just overwrite the data -} - -//--------------------------------------------------------------------------- -// id3v1tag Destructor - -id3v1tag::~id3v1tag() -{ -} - -//--------------------------------------------------------------------------- -// id3v1tag::album -// -// Sets the ALBUM tag element -// -// Arguments: -// -// album - Value to set the ALBUM tag element to - -void id3v1tag::album(char const* album) -{ - if (album == nullptr) - album = ""; - - memset(&m_tag.album, 0, std::extent::value); - size_t len = std::min(strlen(album), std::extent::value); - memcpy(&m_tag.album, album, len); -} - -//--------------------------------------------------------------------------- -// id3v1tag::artist -// -// Sets the ARTIST tag element -// -// Arguments: -// -// artist - Value to set the ARTIST tag element to - -void id3v1tag::artist(char const* artist) -{ - if (artist == nullptr) - artist = ""; - - memset(&m_tag.artist, 0, std::extent::value); - size_t len = std::min(strlen(artist), std::extent::value); - memcpy(&m_tag.artist, artist, len); -} - -//--------------------------------------------------------------------------- -// id3v1tag::comment -// -// Sets the COMMENT tag element -// -// Arguments: -// -// comment - Value to set the COMMENT tag element to - -void id3v1tag::comment(char const* comment) -{ - if (comment == nullptr) - comment = ""; - - // Track number shares the final two bytes of the comment field. If a track - // number has already been set, the comment field becomes 2 bytes shorter - size_t fieldlength = std::extent::value; - if ((m_tag.comment[28] == 0x00) && (m_tag.comment[29] != 0x00)) - fieldlength -= 2; - - memset(&m_tag.comment, 0, fieldlength); - size_t len = std::min(strlen(comment), fieldlength); - memcpy(&m_tag.comment, comment, len); -} - -//--------------------------------------------------------------------------- -// id3v1tag::create (static) -// -// Factory method, creates a new id3v1tag instance -// -// Arguments: -// -// NONE - -std::unique_ptr id3v1tag::create(void) -{ - return std::unique_ptr(new id3v1tag()); -} - -//--------------------------------------------------------------------------- -// id3v1tag::create (static) -// -// Factory method, creates a new id3v1tag instance -// -// Arguments: -// -// data - Pointer to existing ID3v2 tag data -// length - Length of the xisting ID3v2 tag data - -std::unique_ptr id3v1tag::create(uint8_t const* data, size_t length) -{ - return std::unique_ptr(new id3v1tag(data, length)); -} - -//--------------------------------------------------------------------------- -// id3v1tag::genre -// -// Sets the GENRE tag element -// -// Arguments: -// -// genre - Value to set the GENRE tag element to - -void id3v1tag::genre(uint8_t genre) -{ - m_tag.genre = genre; -} - -//--------------------------------------------------------------------------- -// id3v1tag::genre -// -// Sets the GENRE tag element -// -// Arguments: -// -// genre - Value to set the GENRE tag element to - -void id3v1tag::genre(char const* genre) -{ - if (genre == nullptr) - genre = ""; - - // Assume there will not be a matching genre string in the table - uint8_t mapped = UNSPECIFIED_GENRE; - - // Try to match the string-based genre with one of the known values in the table - for (int index = 0; index < g_numgenres; index++) - { - -#ifdef _WINDOWS - if (_stricmp(genre, g_genres[index]) == 0) - mapped = static_cast(index); -#else - if (strcasecmp(genre, g_genres[index]) == 0) - mapped = static_cast(index); -#endif - break; - } - - m_tag.genre = mapped; -} - -//--------------------------------------------------------------------------- -// id3v1tag::size -// -// Gets the number of bytes required to persist the tag to storage -// -// Arguments: -// -// NONE - -size_t id3v1tag::size(void) const -{ - return sizeof(id3v1_tag_t); // Always 128 bytes -} - -//--------------------------------------------------------------------------- -// id3v1tag::song -// -// Sets the SONG tag element -// -// Arguments: -// -// song - Value to set the SONG tag element to - -void id3v1tag::song(char const* song) -{ - if (song == nullptr) - song = ""; - - memset(&m_tag.song, 0, std::extent::value); - size_t len = std::min(strlen(song), std::extent::value); - memcpy(&m_tag.song, song, len); -} - -//--------------------------------------------------------------------------- -// id3v1tag::track -// -// Sets the TRACK tag element -// -// Arguments: -// -// track - Value to set the TRACK tag element to - -void id3v1tag::track(uint8_t track) -{ - // Track number shares the final two bytes of the comment field. Clear an - // existing track number only if comment[28] is already zero - if ((track == 0) && (m_tag.comment[28] == 0x00)) - m_tag.comment[29] = 0x00; - - // Otherwise, take over the final two bytes of the comment field - else - { - - m_tag.comment[28] = 0x00; - m_tag.comment[29] = track; - } -} - -//--------------------------------------------------------------------------- -// id3v1tag::write -// -// Writes the tag into a memory buffer -// -// Arguments: -// -// buffer - Address of the buffer to write the tag data into -// length - Length of the destination buffer in uint8_t units - -bool id3v1tag::write(uint8_t* buffer, size_t length) const -{ - if (buffer == nullptr) - throw std::invalid_argument("buffer"); - if (length < sizeof(id3v1_tag_t)) - throw std::invalid_argument("length"); - - memset(buffer, 0, length); - memcpy(buffer, &m_tag, sizeof(id3v1_tag_t)); - return true; -} - -//--------------------------------------------------------------------------- -// id3v1tag::year -// -// Sets the YEAR tag element -// -// Arguments: -// -// year - Value to set the YEAR tag element to - -void id3v1tag::year(char const* year) -{ - if (year == nullptr) - year = ""; - - memset(&m_tag.year, 0, std::extent::value); - size_t len = std::min(strlen(year), std::extent::value); - memcpy(&m_tag.year, year, len); -} - -//--------------------------------------------------------------------------- - -#pragma warning(pop) +//--------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//--------------------------------------------------------------------------- + +#include "id3v1tag.h" + +#include "stdafx.h" +#include "string_exception.h" + +#include +#include +#include + +#pragma warning(push, 4) + +// g_genres +// +// List of ID3v1 genre strings. Note that this list is not exhaustive and +// only implements the original 79 genres specified by ID3v1: +// +// https://www.oreilly.com/library/view/mp3-the-definitive/1565926617/apa.html +static char const* const g_genres[] = { + + "Blues", + "Classic Rock", + "Country", + "Dance", + "Disco", + "Funk", + "Grunge", + "Hip-Hop", + "Jazz", + "Metal", + "New Age", + "Oldies", + "Other", + "Pop", + "R&B", + "Rap", + "Reggae", + "Rock", + "Techno", + "Industrial", + "Alternative", + "Ska", + "Death Metal", + "Pranks", + "Soundtrack", + "Euro-Techno", + "Ambient", + "Trip-Hop", + "Vocal", + "Jazz+Funk", + "Fusion", + "Trance", + "Classical", + "Instrumental", + "Acid", + "House", + "Game", + "Sound Clip", + "Gospel", + "Noise", + "AlternRock", + "Bass", + "Soul", + "Punk", + "Space", + "Meditative", + "Instrumental Pop", + "Instrumental Rock", + "Ethnic", + "Gothic", + "Darkwave", + "Techno-Industrial", + "Electronic", + "Pop-Folk", + "Eurodance", + "Dream", + "Southern Rock", + "Comedy", + "Cult", + "Gangsta", + "Top 40", + "Christian Rap", + "Pop/Funk", + "Jungle", + "Native American", + "Cabaret", + "New Wave", + "Psychedelic", + "Rave", + "Showtunes", + "Trailer", + "Lo-Fi", + "Tribal", + "Acid Punk", + "Acid Jazz", + "Polka", + "Retro", + "Musical", + "Rock & Roll", + "Hard Rock", +}; + +// g_numgenres +// +// Indicates the number of genres in g_genres +static int const g_numgenres = sizeof(g_genres) / sizeof(g_genres[0]); + +// id3v1tag::UNSPECIFIED_GENRE +// +// Flag indicating that a genre is unspecified +uint8_t const id3v1tag::UNSPECIFIED_GENRE = 255; + +//--------------------------------------------------------------------------- +// id3v1tag Constructor (private) +// +// Arguments: +// +// NONE + +id3v1tag::id3v1tag() +{ + memcpy(m_tag.id, "TAG", 3); + m_tag.genre = UNSPECIFIED_GENRE; +} + +//--------------------------------------------------------------------------- +// id3v1tag Constructor (private) +// +// Arguments: +// +// data - Pointer to existing ID3v1 tag data +// length - Length of existing ID3v1 tag data + +id3v1tag::id3v1tag(uint8_t const* data, size_t length) +{ + if (data == nullptr) + throw std::invalid_argument("data"); + if (length != sizeof(id3v1_tag_t)) + throw std::invalid_argument("length"); + + if (memcmp(data, "TAG", 3) != 0) + throw string_exception("invalid ID3v1 header"); + + memcpy(&m_tag, data, length); // Just overwrite the data +} + +//--------------------------------------------------------------------------- +// id3v1tag Destructor + +id3v1tag::~id3v1tag() +{ +} + +//--------------------------------------------------------------------------- +// id3v1tag::album +// +// Sets the ALBUM tag element +// +// Arguments: +// +// album - Value to set the ALBUM tag element to + +void id3v1tag::album(char const* album) +{ + if (album == nullptr) + album = ""; + + memset(&m_tag.album, 0, std::extent::value); + size_t len = std::min(strlen(album), std::extent::value); + memcpy(&m_tag.album, album, len); +} + +//--------------------------------------------------------------------------- +// id3v1tag::artist +// +// Sets the ARTIST tag element +// +// Arguments: +// +// artist - Value to set the ARTIST tag element to + +void id3v1tag::artist(char const* artist) +{ + if (artist == nullptr) + artist = ""; + + memset(&m_tag.artist, 0, std::extent::value); + size_t len = std::min(strlen(artist), std::extent::value); + memcpy(&m_tag.artist, artist, len); +} + +//--------------------------------------------------------------------------- +// id3v1tag::comment +// +// Sets the COMMENT tag element +// +// Arguments: +// +// comment - Value to set the COMMENT tag element to + +void id3v1tag::comment(char const* comment) +{ + if (comment == nullptr) + comment = ""; + + // Track number shares the final two bytes of the comment field. If a track + // number has already been set, the comment field becomes 2 bytes shorter + size_t fieldlength = std::extent::value; + if ((m_tag.comment[28] == 0x00) && (m_tag.comment[29] != 0x00)) + fieldlength -= 2; + + memset(&m_tag.comment, 0, fieldlength); + size_t len = std::min(strlen(comment), fieldlength); + memcpy(&m_tag.comment, comment, len); +} + +//--------------------------------------------------------------------------- +// id3v1tag::create (static) +// +// Factory method, creates a new id3v1tag instance +// +// Arguments: +// +// NONE + +std::unique_ptr id3v1tag::create(void) +{ + return std::unique_ptr(new id3v1tag()); +} + +//--------------------------------------------------------------------------- +// id3v1tag::create (static) +// +// Factory method, creates a new id3v1tag instance +// +// Arguments: +// +// data - Pointer to existing ID3v2 tag data +// length - Length of the xisting ID3v2 tag data + +std::unique_ptr id3v1tag::create(uint8_t const* data, size_t length) +{ + return std::unique_ptr(new id3v1tag(data, length)); +} + +//--------------------------------------------------------------------------- +// id3v1tag::genre +// +// Sets the GENRE tag element +// +// Arguments: +// +// genre - Value to set the GENRE tag element to + +void id3v1tag::genre(uint8_t genre) +{ + m_tag.genre = genre; +} + +//--------------------------------------------------------------------------- +// id3v1tag::genre +// +// Sets the GENRE tag element +// +// Arguments: +// +// genre - Value to set the GENRE tag element to + +void id3v1tag::genre(char const* genre) +{ + if (genre == nullptr) + genre = ""; + + // Assume there will not be a matching genre string in the table + uint8_t mapped = UNSPECIFIED_GENRE; + + // Try to match the string-based genre with one of the known values in the table + for (int index = 0; index < g_numgenres; index++) + { + +#ifdef _WINDOWS + if (_stricmp(genre, g_genres[index]) == 0) + mapped = static_cast(index); +#else + if (strcasecmp(genre, g_genres[index]) == 0) + mapped = static_cast(index); +#endif + break; + } + + m_tag.genre = mapped; +} + +//--------------------------------------------------------------------------- +// id3v1tag::size +// +// Gets the number of bytes required to persist the tag to storage +// +// Arguments: +// +// NONE + +size_t id3v1tag::size(void) const +{ + return sizeof(id3v1_tag_t); // Always 128 bytes +} + +//--------------------------------------------------------------------------- +// id3v1tag::song +// +// Sets the SONG tag element +// +// Arguments: +// +// song - Value to set the SONG tag element to + +void id3v1tag::song(char const* song) +{ + if (song == nullptr) + song = ""; + + memset(&m_tag.song, 0, std::extent::value); + size_t len = std::min(strlen(song), std::extent::value); + memcpy(&m_tag.song, song, len); +} + +//--------------------------------------------------------------------------- +// id3v1tag::track +// +// Sets the TRACK tag element +// +// Arguments: +// +// track - Value to set the TRACK tag element to + +void id3v1tag::track(uint8_t track) +{ + // Track number shares the final two bytes of the comment field. Clear an + // existing track number only if comment[28] is already zero + if ((track == 0) && (m_tag.comment[28] == 0x00)) + m_tag.comment[29] = 0x00; + + // Otherwise, take over the final two bytes of the comment field + else + { + + m_tag.comment[28] = 0x00; + m_tag.comment[29] = track; + } +} + +//--------------------------------------------------------------------------- +// id3v1tag::write +// +// Writes the tag into a memory buffer +// +// Arguments: +// +// buffer - Address of the buffer to write the tag data into +// length - Length of the destination buffer in uint8_t units + +bool id3v1tag::write(uint8_t* buffer, size_t length) const +{ + if (buffer == nullptr) + throw std::invalid_argument("buffer"); + if (length < sizeof(id3v1_tag_t)) + throw std::invalid_argument("length"); + + memset(buffer, 0, length); + memcpy(buffer, &m_tag, sizeof(id3v1_tag_t)); + return true; +} + +//--------------------------------------------------------------------------- +// id3v1tag::year +// +// Sets the YEAR tag element +// +// Arguments: +// +// year - Value to set the YEAR tag element to + +void id3v1tag::year(char const* year) +{ + if (year == nullptr) + year = ""; + + memset(&m_tag.year, 0, std::extent::value); + size_t len = std::min(strlen(year), std::extent::value); + memcpy(&m_tag.year, year, len); +} + +//--------------------------------------------------------------------------- + +#pragma warning(pop) diff --git a/src/id3v1tag.h b/src/id3v1tag.h index bfb511d..7d79314 100644 --- a/src/id3v1tag.h +++ b/src/id3v1tag.h @@ -1,139 +1,139 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//----------------------------------------------------------------------------- - -#ifndef __ID3V1TAG_H_ -#define __ID3V1TAG_H_ -#pragma once - -#include - -#pragma warning(push, 4) - -//--------------------------------------------------------------------------- -// Class id3v1tag -// -// Implements a simple ID3v1 tag generator - -class id3v1tag -{ -public: - // Destructor - // - ~id3v1tag(); - - //----------------------------------------------------------------------- - // Member Functions - - // album - // - // Sets the ALBUM tag element - void album(char const* album); - - // artist - // - // Sets the ARTIST tag element - void artist(char const* artist); - - // comment - // - // Sets the COMMENT tag element - void comment(char const* comment); - - // create (static) - // - // Factory method, creates a new id3v1tag instance - static std::unique_ptr create(void); - static std::unique_ptr create(uint8_t const* data, size_t length); - - // genre - // - // Sets the GENRE tag element - void genre(uint8_t genre); - void genre(char const* genre); - - // size - // - // Gets the number of bytes required to persist the tag to storage - size_t size(void) const; - - // song - // - // Sets the SONG tag element - void song(char const* song); - - // track - // - // Sets the TRACK tag element - void track(uint8_t track); - - // write - // - // Writes the tag into a memory buffer - bool write(uint8_t* buffer, size_t length) const; - - // year - // - // Sets the YEAR tag element - void year(char const* year); - -private: - id3v1tag(id3v1tag const&) = delete; - id3v1tag& operator=(id3v1tag const&) = delete; - - // id3v1_tag_t - // - // Defines the ID3v1 tag structure - struct id3v1_tag_t - { - - uint8_t id[3]; - uint8_t song[30]; - uint8_t artist[30]; - uint8_t album[30]; - uint8_t year[4]; - uint8_t comment[30]; - uint8_t genre; - }; - - static_assert(sizeof(id3v1_tag_t) == 128, "id3v1_tag_t must be 128 bytes in length"); - - // Instance Constructors - // - id3v1tag(); - id3v1tag(uint8_t const* data, size_t length); - - // UNSPECIFIED_GENRE - // - // Flag indicating that a genre is unspecified - static uint8_t const UNSPECIFIED_GENRE; - - //----------------------------------------------------------------------- - // Member Variables - - id3v1_tag_t m_tag = {}; // ID3v1 tag information -}; - -//----------------------------------------------------------------------------- - -#pragma warning(pop) - -#endif // __ID3V1TAG_H_ +//----------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef __ID3V1TAG_H_ +#define __ID3V1TAG_H_ +#pragma once + +#include + +#pragma warning(push, 4) + +//--------------------------------------------------------------------------- +// Class id3v1tag +// +// Implements a simple ID3v1 tag generator + +class id3v1tag +{ +public: + // Destructor + // + ~id3v1tag(); + + //----------------------------------------------------------------------- + // Member Functions + + // album + // + // Sets the ALBUM tag element + void album(char const* album); + + // artist + // + // Sets the ARTIST tag element + void artist(char const* artist); + + // comment + // + // Sets the COMMENT tag element + void comment(char const* comment); + + // create (static) + // + // Factory method, creates a new id3v1tag instance + static std::unique_ptr create(void); + static std::unique_ptr create(uint8_t const* data, size_t length); + + // genre + // + // Sets the GENRE tag element + void genre(uint8_t genre); + void genre(char const* genre); + + // size + // + // Gets the number of bytes required to persist the tag to storage + size_t size(void) const; + + // song + // + // Sets the SONG tag element + void song(char const* song); + + // track + // + // Sets the TRACK tag element + void track(uint8_t track); + + // write + // + // Writes the tag into a memory buffer + bool write(uint8_t* buffer, size_t length) const; + + // year + // + // Sets the YEAR tag element + void year(char const* year); + +private: + id3v1tag(id3v1tag const&) = delete; + id3v1tag& operator=(id3v1tag const&) = delete; + + // id3v1_tag_t + // + // Defines the ID3v1 tag structure + struct id3v1_tag_t + { + + uint8_t id[3]; + uint8_t song[30]; + uint8_t artist[30]; + uint8_t album[30]; + uint8_t year[4]; + uint8_t comment[30]; + uint8_t genre; + }; + + static_assert(sizeof(id3v1_tag_t) == 128, "id3v1_tag_t must be 128 bytes in length"); + + // Instance Constructors + // + id3v1tag(); + id3v1tag(uint8_t const* data, size_t length); + + // UNSPECIFIED_GENRE + // + // Flag indicating that a genre is unspecified + static uint8_t const UNSPECIFIED_GENRE; + + //----------------------------------------------------------------------- + // Member Variables + + id3v1_tag_t m_tag = {}; // ID3v1 tag information +}; + +//----------------------------------------------------------------------------- + +#pragma warning(pop) + +#endif // __ID3V1TAG_H_ diff --git a/src/id3v2tag.cpp b/src/id3v2tag.cpp index 7092d07..2e34355 100644 --- a/src/id3v2tag.cpp +++ b/src/id3v2tag.cpp @@ -1,502 +1,502 @@ -//--------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//--------------------------------------------------------------------------- - -#include "id3v2tag.h" - -#include "stdafx.h" -#include "string_exception.h" - -#include -#include -#include - -#ifdef _WINDOWS -#define strcasecmp _stricmp -#define strncasecmp _strnicmp -#endif // _WINDOWS - -#pragma warning(push, 4) - -//--------------------------------------------------------------------------- -// id3v2tag Constructor (private) -// -// Arguments: -// -// NONE - -id3v2tag::id3v2tag() -{ -} - -//--------------------------------------------------------------------------- -// id3v2tag Constructor (private) -// -// Arguments: -// -// data - Pointer to existing ID3v2 tag data -// length - Length of existing ID3v2 tag data - -id3v2tag::id3v2tag(uint8_t const* data, size_t length) -{ - if (data == nullptr) - throw std::invalid_argument("data"); - if (length < sizeof(id3v2_header_t)) - throw std::invalid_argument("length"); - - // Cast a pointer to the ID3v2 tag header and check some invariants - id3v2_header_t const* header = reinterpret_cast(data); - - if (memcmp(&header->id, "ID3", 3) != 0) - throw string_exception("invalid ID3v2 header"); - if (header->version > 4) - throw string_exception("invalid ID3v2 version"); - if (length < (sizeof(id3v2_header_t) + decode_synchsafe(header->size))) - throw string_exception("truncated ID3v2 tag"); - - // Skip over the header and extended header (if present) - size_t offset = - sizeof(id3v2_header_t) + - ((header->extendedheader) ? decode_synchsafe(reinterpret_cast( - &data[sizeof(id3v2_header_t)]) - ->size) - : 0); - - // Disregard the footer (if present), its length is the same as the header (10 bytes) - if (header->footer) - length -= sizeof(id3v2_header_t); - - // Generate a vector<> of the existing ID3 frame tag data - while (offset < length) - { - - id3v2_frame_header_t const* frame = - reinterpret_cast(&data[offset]); - uint32_t framesize = decode_synchsafe(frame->size); - - frame_t newframe = {}; - memcpy(&newframe.id, &frame->id, sizeof(id3v2_frameid_t)); - newframe.flags[0] = frame->flags[0]; - newframe.flags[1] = frame->flags[1]; - newframe.size = framesize; - newframe.data = std::unique_ptr(new uint8_t[newframe.size]); - memcpy(newframe.data.get(), &data[offset + sizeof(id3v2_frame_header_t)], newframe.size); - m_frames.emplace_back(std::move(newframe)); - - offset += sizeof(id3v2_frame_header_t) + framesize; - } - - // All of the existing ID3v2 tag information should have been ingested successfully ... - assert(offset == length); - if (offset != length) - throw string_exception("truncated ID3v2 tag"); -} - -//--------------------------------------------------------------------------- -// id3v2tag Destructor - -id3v2tag::~id3v2tag() -{ -} - -//--------------------------------------------------------------------------- -// id3v2tag::add_text_frame (private) -// -// Adds an ID3v2 text frame -// -// Arguments: -// -// frameid - Frame identifier -// text - ISO-8859-1 text string -// append - Flag to append a new tag rather than replace existing tag - -void id3v2tag::add_text_frame(id3v2_frameid_t frameid, char const* text, bool append) -{ - assert(frameid[0] == 'T'); - - if (!append) - remove_frames(frameid); - - if (text == nullptr) - return; - size_t textlength = strlen(text); - - // encoding | text | terminator - frame_t frame = {}; - memcpy(frame.id, frameid, sizeof(id3v2_frameid_t)); - frame.size = 1 + textlength + 1; - frame.data = std::unique_ptr(new uint8_t[frame.size]); - - frame.data[0] = 0x00; // ISO-8859-1 - if (textlength > 0) - memcpy(&frame.data[1], text, textlength); // Text - frame.data[textlength + 1] = 0x00; // NULL terminator - - m_frames.emplace_back(std::move(frame)); -} - -//--------------------------------------------------------------------------- -// id3v2tag::album -// -// Sets the album (TALB) frame -// -// Arguments: -// -// album - ISO-8859-1 text string to set for the frame - -void id3v2tag::album(char const* album) -{ - id3v2_frameid_t frameid{'T', 'A', 'L', 'B'}; - add_text_frame(frameid, album, false); -} - -//--------------------------------------------------------------------------- -// id3v2tag::artist -// -// Sets or appends an artist (TPE1) frame -// -// Arguments: -// -// artist - ISO-8859-1 text string to set for the frame -// append - Flag to append a new tag rather than replace existing - -void id3v2tag::artist(char const* artist, bool append) -{ - id3v2_frameid_t frameid{'T', 'P', 'E', '1'}; - add_text_frame(frameid, artist, append); -} - -//--------------------------------------------------------------------------- -// id3v2tag::comment -// -// Sets the comment (COMM; no content descriptor) frame -// -// Arguments: -// -// comment - ISO-8859-1 text string to set for the frame - -void id3v2tag::comment(char const* comment) -{ - id3v2_frameid_t frameid{'C', 'O', 'M', 'M'}; - - // Remove any existing COMM frames with no description from the tag - frame_vector_t::iterator it = m_frames.begin(); - while (it != m_frames.end()) - { - - if (strncasecmp(it->id, frameid, sizeof(id3v2_frameid_t)) == 0) - { - - // If the fourth byte of the COMM frame is zero ([0] = encoding, [1-3] = language), - // this is a main/no description comment, remove it - if ((it->size >= 4) && (it->data[4] == 0x00)) - it = m_frames.erase(it); - } - else - it++; - } - - if (comment == nullptr) - return; - size_t commentlength = strlen(comment); - - // encoding | language | content descriptor | text | terminator - frame_t frame = {}; - memcpy(frame.id, frameid, sizeof(id3v2_frameid_t)); - frame.size = 1 + 3 + 1 + commentlength + 1; - frame.data = std::unique_ptr(new uint8_t[frame.size]); - - frame.data[0] = 0x00; // ISO-8859-1 - memcpy(&frame.data[1], "und", 3); // ISO-639-2 - frame.data[4] = 0x00; // NULL descriptor - memcpy(&frame.data[5], comment, commentlength); // Text - frame.data[5 + commentlength] = 0x00; // NULL terminator - - m_frames.emplace_back(std::move(frame)); -} - -//--------------------------------------------------------------------------- -// id3v2tag::coverart -// -// Sets the cover art (APIC; type 0x03) frame -// -// Arguments: -// -// mimetype - MIME type of the cover art image -// data - Pointer to the cover art image data -// length - Length of the cover art image data - -void id3v2tag::coverart(char const* mimetype, uint8_t const* data, size_t length) -{ - id3v2_frameid_t frameid{'A', 'P', 'I', 'C'}; - - // Remove any existing APIC cover art frames (type 0x03) from the tag - frame_vector_t::iterator it = m_frames.begin(); - while (it != m_frames.end()) - { - - if ((strncasecmp(it->id, frameid, sizeof(id3v2_frameid_t)) == 0) && - (it->data[strlen(reinterpret_cast(&it->data[1])) + 1] == 0x03)) - it = m_frames.erase(it); - else - it++; - } - - if ((data == nullptr) || (length == 0)) - return; - - if (mimetype == nullptr) - mimetype = "image/"; - size_t mimetypelen = strlen(mimetype); - - // encoding | mimetype | picturetype | description | data - frame_t frame = {}; - memcpy(frame.id, frameid, sizeof(id3v2_frameid_t)); - frame.size = 1 + mimetypelen + 1 + 1 + 1 + length; - frame.data = std::unique_ptr(new uint8_t[frame.size]); - - frame.data[0] = 0x00; // ISO-8859-1 - memcpy(&frame.data[1], mimetype, mimetypelen); // MIME type - frame.data[1 + mimetypelen] = 0x00; // NULL terminator - frame.data[2 + mimetypelen] = 0x03; // Picture type - frame.data[3 + mimetypelen] = 0x00; // Description (NULL) - memcpy(&frame.data[4 + mimetypelen], data, length); // Image data - - m_frames.emplace_back(std::move(frame)); -} - -//--------------------------------------------------------------------------- -// id3v2tag::create (static) -// -// Factory method, creates a new id3v2tag instance -// -// Arguments: -// -// NONE - -std::unique_ptr id3v2tag::create(void) -{ - return std::unique_ptr(new id3v2tag()); -} - -//--------------------------------------------------------------------------- -// id3v2tag::create (static) -// -// Factory method, creates a new id3v2tag instance -// -// Arguments: -// -// data - Pointer to existing ID3v2 tag data -// length - Length of the xisting ID3v2 tag data - -std::unique_ptr id3v2tag::create(uint8_t const* data, size_t length) -{ - return std::unique_ptr(new id3v2tag(data, length)); -} - -//--------------------------------------------------------------------------- -// id3v2tag::decode_synchsafe (static, private) -// -// Decodes a 32-bit synchsafe integer -// -// Arguments: -// -// synchsafe - Reference to a 32-bit synchsafe integer - -uint32_t id3v2tag::decode_synchsafe(synchsafe32_t const& synchsafe) -{ - uint8_t const* bytes = reinterpret_cast(&synchsafe); - - return ((bytes[0] & 0x7f) << 21) | ((bytes[1] & 0x7f) << 14) | ((bytes[2] & 0x7f) << 7) | - (bytes[3] & 0x7f); -} - -//--------------------------------------------------------------------------- -// id3v2tag::encode_synchsafe (static, private) -// -// Encodes a 32-bit synchsafe integer -// -// Arguments: -// -// val - 32-bit value to be encoded -// synchsafe - Reference to the encoded synchsafe32_t variable - -void id3v2tag::encode_synchsafe(uint32_t val, synchsafe32_t& synchsafe) -{ - uint8_t* bytes = reinterpret_cast(&synchsafe); - - bytes[0] = (val >> 21) & 0x7F; - bytes[1] = (val >> 14) & 0x7F; - bytes[2] = (val >> 7) & 0x7F; - bytes[3] = val & 0x7F; -} - -//--------------------------------------------------------------------------- -// id3v2tag::genre -// -// Sets or appends a genre (TCON) frame -// -// Arguments: -// -// genre - ISO-8859-1 text string to set for the frame -// append - Flag to append a new tag rather than replace existing - -void id3v2tag::genre(char const* artist, bool append) -{ - id3v2_frameid_t frameid{'T', 'C', 'O', 'N'}; - add_text_frame(frameid, artist, append); -} - -//--------------------------------------------------------------------------- -// id3v2tag::remove_frames (private) -// -// Removes all existing frames with the specified identifier -// -// Arguments: -// -// frameid - Frame identifier to be removed from the tag - -void id3v2tag::remove_frames(id3v2_frameid_t frameid) -{ - frame_vector_t::iterator it = m_frames.begin(); - while (it != m_frames.end()) - { - - if (strncasecmp(it->id, frameid, sizeof(id3v2_frameid_t)) == 0) - it = m_frames.erase(it); - else - it++; - } -} - -//--------------------------------------------------------------------------- -// id3v2tag::size -// -// Gets the number of bytes required to persist the tag to storage -// -// Arguments: -// -// NONE - -size_t id3v2tag::size(void) const -{ - size_t size = sizeof(id3v2_header_t); - for (auto const& frame : m_frames) - size += sizeof(id3v2_frame_header_t) + frame.size; - - return size; -} - -//--------------------------------------------------------------------------- -// id3v2tag::title -// -// Sets the title (TIT2) frame -// -// Arguments: -// -// title - ISO-8859-1 text string to set for the frame - -void id3v2tag::title(char const* title) -{ - id3v2_frameid_t frameid{'T', 'I', 'T', '2'}; - add_text_frame(frameid, title, false); -} - -//--------------------------------------------------------------------------- -// id3v2tag::track -// -// Sets the track (TRCK) frame -// -// Arguments: -// -// track - ISO-8859-1 text string to set for the frame - -void id3v2tag::track(char const* track) -{ - id3v2_frameid_t frameid{'T', 'R', 'C', 'K'}; - add_text_frame(frameid, track, false); -} - -//--------------------------------------------------------------------------- -// id3v2tag::write -// -// Writes the tag into a memory buffer -// -// Arguments: -// -// buffer - Address of the buffer to write the tag data into -// length - Length of the destination buffer in uint8_t units - -bool id3v2tag::write(uint8_t* buffer, size_t length) const -{ - if (buffer == nullptr) - throw std::invalid_argument("buffer"); - if (length < sizeof(id3v2_header_t)) - throw std::invalid_argument("length"); - - uint32_t tagsize = static_cast(size()); - if (length < tagsize) - return false; - - memset(buffer, 0, length); - - // Header - id3v2_header_t* header = reinterpret_cast(buffer); - memcpy(header->id, "ID3", 3); - header->version = 0x04; - encode_synchsafe(tagsize, header->size); - buffer += sizeof(id3v2_header_t); - - // Frames - for (auto const& frame : m_frames) - { - - id3v2_frame_header_t* frameheader = reinterpret_cast(buffer); - memcpy(frameheader->id, frame.id, 4); - encode_synchsafe(static_cast(frame.size), frameheader->size); - memcpy(frameheader->flags, frame.flags, 2); - buffer += sizeof(id3v2_frame_header_t); - - memcpy(buffer, &frame.data[0], frame.size); - buffer += frame.size; - } - - return true; -} - -//--------------------------------------------------------------------------- -// id3v2tag::year -// -// Sets the year (TYER) frame -// -// Arguments: -// -// year - ISO-8859-1 text string to set for the frame - -void id3v2tag::year(char const* year) -{ - id3v2_frameid_t frameid{'T', 'Y', 'E', 'R'}; - add_text_frame(frameid, year, false); -} - -//--------------------------------------------------------------------------- - -#pragma warning(pop) +//--------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//--------------------------------------------------------------------------- + +#include "id3v2tag.h" + +#include "stdafx.h" +#include "string_exception.h" + +#include +#include +#include + +#ifdef _WINDOWS +#define strcasecmp _stricmp +#define strncasecmp _strnicmp +#endif // _WINDOWS + +#pragma warning(push, 4) + +//--------------------------------------------------------------------------- +// id3v2tag Constructor (private) +// +// Arguments: +// +// NONE + +id3v2tag::id3v2tag() +{ +} + +//--------------------------------------------------------------------------- +// id3v2tag Constructor (private) +// +// Arguments: +// +// data - Pointer to existing ID3v2 tag data +// length - Length of existing ID3v2 tag data + +id3v2tag::id3v2tag(uint8_t const* data, size_t length) +{ + if (data == nullptr) + throw std::invalid_argument("data"); + if (length < sizeof(id3v2_header_t)) + throw std::invalid_argument("length"); + + // Cast a pointer to the ID3v2 tag header and check some invariants + id3v2_header_t const* header = reinterpret_cast(data); + + if (memcmp(&header->id, "ID3", 3) != 0) + throw string_exception("invalid ID3v2 header"); + if (header->version > 4) + throw string_exception("invalid ID3v2 version"); + if (length < (sizeof(id3v2_header_t) + decode_synchsafe(header->size))) + throw string_exception("truncated ID3v2 tag"); + + // Skip over the header and extended header (if present) + size_t offset = + sizeof(id3v2_header_t) + + ((header->extendedheader) ? decode_synchsafe(reinterpret_cast( + &data[sizeof(id3v2_header_t)]) + ->size) + : 0); + + // Disregard the footer (if present), its length is the same as the header (10 bytes) + if (header->footer) + length -= sizeof(id3v2_header_t); + + // Generate a vector<> of the existing ID3 frame tag data + while (offset < length) + { + + id3v2_frame_header_t const* frame = + reinterpret_cast(&data[offset]); + uint32_t framesize = decode_synchsafe(frame->size); + + frame_t newframe = {}; + memcpy(&newframe.id, &frame->id, sizeof(id3v2_frameid_t)); + newframe.flags[0] = frame->flags[0]; + newframe.flags[1] = frame->flags[1]; + newframe.size = framesize; + newframe.data = std::unique_ptr(new uint8_t[newframe.size]); + memcpy(newframe.data.get(), &data[offset + sizeof(id3v2_frame_header_t)], newframe.size); + m_frames.emplace_back(std::move(newframe)); + + offset += sizeof(id3v2_frame_header_t) + framesize; + } + + // All of the existing ID3v2 tag information should have been ingested successfully ... + assert(offset == length); + if (offset != length) + throw string_exception("truncated ID3v2 tag"); +} + +//--------------------------------------------------------------------------- +// id3v2tag Destructor + +id3v2tag::~id3v2tag() +{ +} + +//--------------------------------------------------------------------------- +// id3v2tag::add_text_frame (private) +// +// Adds an ID3v2 text frame +// +// Arguments: +// +// frameid - Frame identifier +// text - ISO-8859-1 text string +// append - Flag to append a new tag rather than replace existing tag + +void id3v2tag::add_text_frame(id3v2_frameid_t frameid, char const* text, bool append) +{ + assert(frameid[0] == 'T'); + + if (!append) + remove_frames(frameid); + + if (text == nullptr) + return; + size_t textlength = strlen(text); + + // encoding | text | terminator + frame_t frame = {}; + memcpy(frame.id, frameid, sizeof(id3v2_frameid_t)); + frame.size = 1 + textlength + 1; + frame.data = std::unique_ptr(new uint8_t[frame.size]); + + frame.data[0] = 0x00; // ISO-8859-1 + if (textlength > 0) + memcpy(&frame.data[1], text, textlength); // Text + frame.data[textlength + 1] = 0x00; // NULL terminator + + m_frames.emplace_back(std::move(frame)); +} + +//--------------------------------------------------------------------------- +// id3v2tag::album +// +// Sets the album (TALB) frame +// +// Arguments: +// +// album - ISO-8859-1 text string to set for the frame + +void id3v2tag::album(char const* album) +{ + id3v2_frameid_t frameid{'T', 'A', 'L', 'B'}; + add_text_frame(frameid, album, false); +} + +//--------------------------------------------------------------------------- +// id3v2tag::artist +// +// Sets or appends an artist (TPE1) frame +// +// Arguments: +// +// artist - ISO-8859-1 text string to set for the frame +// append - Flag to append a new tag rather than replace existing + +void id3v2tag::artist(char const* artist, bool append) +{ + id3v2_frameid_t frameid{'T', 'P', 'E', '1'}; + add_text_frame(frameid, artist, append); +} + +//--------------------------------------------------------------------------- +// id3v2tag::comment +// +// Sets the comment (COMM; no content descriptor) frame +// +// Arguments: +// +// comment - ISO-8859-1 text string to set for the frame + +void id3v2tag::comment(char const* comment) +{ + id3v2_frameid_t frameid{'C', 'O', 'M', 'M'}; + + // Remove any existing COMM frames with no description from the tag + frame_vector_t::iterator it = m_frames.begin(); + while (it != m_frames.end()) + { + + if (strncasecmp(it->id, frameid, sizeof(id3v2_frameid_t)) == 0) + { + + // If the fourth byte of the COMM frame is zero ([0] = encoding, [1-3] = language), + // this is a main/no description comment, remove it + if ((it->size >= 4) && (it->data[4] == 0x00)) + it = m_frames.erase(it); + } + else + it++; + } + + if (comment == nullptr) + return; + size_t commentlength = strlen(comment); + + // encoding | language | content descriptor | text | terminator + frame_t frame = {}; + memcpy(frame.id, frameid, sizeof(id3v2_frameid_t)); + frame.size = 1 + 3 + 1 + commentlength + 1; + frame.data = std::unique_ptr(new uint8_t[frame.size]); + + frame.data[0] = 0x00; // ISO-8859-1 + memcpy(&frame.data[1], "und", 3); // ISO-639-2 + frame.data[4] = 0x00; // NULL descriptor + memcpy(&frame.data[5], comment, commentlength); // Text + frame.data[5 + commentlength] = 0x00; // NULL terminator + + m_frames.emplace_back(std::move(frame)); +} + +//--------------------------------------------------------------------------- +// id3v2tag::coverart +// +// Sets the cover art (APIC; type 0x03) frame +// +// Arguments: +// +// mimetype - MIME type of the cover art image +// data - Pointer to the cover art image data +// length - Length of the cover art image data + +void id3v2tag::coverart(char const* mimetype, uint8_t const* data, size_t length) +{ + id3v2_frameid_t frameid{'A', 'P', 'I', 'C'}; + + // Remove any existing APIC cover art frames (type 0x03) from the tag + frame_vector_t::iterator it = m_frames.begin(); + while (it != m_frames.end()) + { + + if ((strncasecmp(it->id, frameid, sizeof(id3v2_frameid_t)) == 0) && + (it->data[strlen(reinterpret_cast(&it->data[1])) + 1] == 0x03)) + it = m_frames.erase(it); + else + it++; + } + + if ((data == nullptr) || (length == 0)) + return; + + if (mimetype == nullptr) + mimetype = "image/"; + size_t mimetypelen = strlen(mimetype); + + // encoding | mimetype | picturetype | description | data + frame_t frame = {}; + memcpy(frame.id, frameid, sizeof(id3v2_frameid_t)); + frame.size = 1 + mimetypelen + 1 + 1 + 1 + length; + frame.data = std::unique_ptr(new uint8_t[frame.size]); + + frame.data[0] = 0x00; // ISO-8859-1 + memcpy(&frame.data[1], mimetype, mimetypelen); // MIME type + frame.data[1 + mimetypelen] = 0x00; // NULL terminator + frame.data[2 + mimetypelen] = 0x03; // Picture type + frame.data[3 + mimetypelen] = 0x00; // Description (NULL) + memcpy(&frame.data[4 + mimetypelen], data, length); // Image data + + m_frames.emplace_back(std::move(frame)); +} + +//--------------------------------------------------------------------------- +// id3v2tag::create (static) +// +// Factory method, creates a new id3v2tag instance +// +// Arguments: +// +// NONE + +std::unique_ptr id3v2tag::create(void) +{ + return std::unique_ptr(new id3v2tag()); +} + +//--------------------------------------------------------------------------- +// id3v2tag::create (static) +// +// Factory method, creates a new id3v2tag instance +// +// Arguments: +// +// data - Pointer to existing ID3v2 tag data +// length - Length of the xisting ID3v2 tag data + +std::unique_ptr id3v2tag::create(uint8_t const* data, size_t length) +{ + return std::unique_ptr(new id3v2tag(data, length)); +} + +//--------------------------------------------------------------------------- +// id3v2tag::decode_synchsafe (static, private) +// +// Decodes a 32-bit synchsafe integer +// +// Arguments: +// +// synchsafe - Reference to a 32-bit synchsafe integer + +uint32_t id3v2tag::decode_synchsafe(synchsafe32_t const& synchsafe) +{ + uint8_t const* bytes = reinterpret_cast(&synchsafe); + + return ((bytes[0] & 0x7f) << 21) | ((bytes[1] & 0x7f) << 14) | ((bytes[2] & 0x7f) << 7) | + (bytes[3] & 0x7f); +} + +//--------------------------------------------------------------------------- +// id3v2tag::encode_synchsafe (static, private) +// +// Encodes a 32-bit synchsafe integer +// +// Arguments: +// +// val - 32-bit value to be encoded +// synchsafe - Reference to the encoded synchsafe32_t variable + +void id3v2tag::encode_synchsafe(uint32_t val, synchsafe32_t& synchsafe) +{ + uint8_t* bytes = reinterpret_cast(&synchsafe); + + bytes[0] = (val >> 21) & 0x7F; + bytes[1] = (val >> 14) & 0x7F; + bytes[2] = (val >> 7) & 0x7F; + bytes[3] = val & 0x7F; +} + +//--------------------------------------------------------------------------- +// id3v2tag::genre +// +// Sets or appends a genre (TCON) frame +// +// Arguments: +// +// genre - ISO-8859-1 text string to set for the frame +// append - Flag to append a new tag rather than replace existing + +void id3v2tag::genre(char const* artist, bool append) +{ + id3v2_frameid_t frameid{'T', 'C', 'O', 'N'}; + add_text_frame(frameid, artist, append); +} + +//--------------------------------------------------------------------------- +// id3v2tag::remove_frames (private) +// +// Removes all existing frames with the specified identifier +// +// Arguments: +// +// frameid - Frame identifier to be removed from the tag + +void id3v2tag::remove_frames(id3v2_frameid_t frameid) +{ + frame_vector_t::iterator it = m_frames.begin(); + while (it != m_frames.end()) + { + + if (strncasecmp(it->id, frameid, sizeof(id3v2_frameid_t)) == 0) + it = m_frames.erase(it); + else + it++; + } +} + +//--------------------------------------------------------------------------- +// id3v2tag::size +// +// Gets the number of bytes required to persist the tag to storage +// +// Arguments: +// +// NONE + +size_t id3v2tag::size(void) const +{ + size_t size = sizeof(id3v2_header_t); + for (auto const& frame : m_frames) + size += sizeof(id3v2_frame_header_t) + frame.size; + + return size; +} + +//--------------------------------------------------------------------------- +// id3v2tag::title +// +// Sets the title (TIT2) frame +// +// Arguments: +// +// title - ISO-8859-1 text string to set for the frame + +void id3v2tag::title(char const* title) +{ + id3v2_frameid_t frameid{'T', 'I', 'T', '2'}; + add_text_frame(frameid, title, false); +} + +//--------------------------------------------------------------------------- +// id3v2tag::track +// +// Sets the track (TRCK) frame +// +// Arguments: +// +// track - ISO-8859-1 text string to set for the frame + +void id3v2tag::track(char const* track) +{ + id3v2_frameid_t frameid{'T', 'R', 'C', 'K'}; + add_text_frame(frameid, track, false); +} + +//--------------------------------------------------------------------------- +// id3v2tag::write +// +// Writes the tag into a memory buffer +// +// Arguments: +// +// buffer - Address of the buffer to write the tag data into +// length - Length of the destination buffer in uint8_t units + +bool id3v2tag::write(uint8_t* buffer, size_t length) const +{ + if (buffer == nullptr) + throw std::invalid_argument("buffer"); + if (length < sizeof(id3v2_header_t)) + throw std::invalid_argument("length"); + + uint32_t tagsize = static_cast(size()); + if (length < tagsize) + return false; + + memset(buffer, 0, length); + + // Header + id3v2_header_t* header = reinterpret_cast(buffer); + memcpy(header->id, "ID3", 3); + header->version = 0x04; + encode_synchsafe(tagsize, header->size); + buffer += sizeof(id3v2_header_t); + + // Frames + for (auto const& frame : m_frames) + { + + id3v2_frame_header_t* frameheader = reinterpret_cast(buffer); + memcpy(frameheader->id, frame.id, 4); + encode_synchsafe(static_cast(frame.size), frameheader->size); + memcpy(frameheader->flags, frame.flags, 2); + buffer += sizeof(id3v2_frame_header_t); + + memcpy(buffer, &frame.data[0], frame.size); + buffer += frame.size; + } + + return true; +} + +//--------------------------------------------------------------------------- +// id3v2tag::year +// +// Sets the year (TYER) frame +// +// Arguments: +// +// year - ISO-8859-1 text string to set for the frame + +void id3v2tag::year(char const* year) +{ + id3v2_frameid_t frameid{'T', 'Y', 'E', 'R'}; + add_text_frame(frameid, year, false); +} + +//--------------------------------------------------------------------------- + +#pragma warning(pop) diff --git a/src/id3v2tag.h b/src/id3v2tag.h index 3c86fe4..8167387 100644 --- a/src/id3v2tag.h +++ b/src/id3v2tag.h @@ -1,245 +1,245 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//----------------------------------------------------------------------------- - -#ifndef __ID3V2TAG_H_ -#define __ID3V2TAG_H_ -#pragma once - -#include -#include - -#pragma warning(push, 4) - -//--------------------------------------------------------------------------- -// Class id3v2tag -// -// Implements a simple ID3v2 tag generator - -class id3v2tag -{ -public: - // Destructor - // - ~id3v2tag(); - - //----------------------------------------------------------------------- - // Member Functions - - // album - // - // Sets the album (TALB) frame - void album(char const* album); - - // artist - // - // Sets or append an artist (TPE1) frame - void artist(char const* artist, bool append = false); - - // comment - // - // Sets the comment (COMM; no content descriptor) frame - void comment(char const* comment); - - // coverart - // - // Sets the cover art (APIC; type 0x03) frame - void coverart(char const* mimetype, uint8_t const* data, size_t length); - - // create (static) - // - // Factory method, creates a new id3v2tag instance - static std::unique_ptr create(void); - static std::unique_ptr create(uint8_t const* data, size_t length); - - // genre - // - // Sets or append an genre (TCON) frame - void genre(char const* artist, bool append = false); - - // size - // - // Gets the number of bytes required to persist the tag to storage - size_t size(void) const; - - // title - // - // Sets the title (TIT2) frame - void title(char const* title); - - // track - // - // Sets the track (TRCK) frame - void track(char const* track); - - // write - // - // Writes the tag into a memory buffer - bool write(uint8_t* buffer, size_t length) const; - - // year - // - // Sets the year (TYER) frame - void year(char const* year); - -private: - id3v2tag(id3v2tag const&) = delete; - id3v2tag& operator=(id3v2tag const&) = delete; - - // synchsafe32_t - // - // A 32-bit synchsafe integer - using synchsafe32_t = uint8_t[4]; - - // id3v2_header_t - // - // Defines the ID3v2 header structure -#ifdef _WINDOWS -#pragma pack(push, 1) - struct id3v2_header_t - { - - uint8_t id[3]; - uint8_t version; - uint8_t revision; - uint8_t footer : 1; - uint8_t experimental : 1; - uint8_t extendedheader : 1; - uint8_t unsynchronization : 1; - uint8_t reserved : 4; - synchsafe32_t size; - }; - - struct id3v2_extended_header_t - { - - synchsafe32_t size; - uint8_t bytes; - uint8_t flags; - }; - - struct id3v2_frame_header_t - { - - uint8_t id[4]; - synchsafe32_t size; - uint8_t flags[2]; - }; -#pragma pack(pop) -#else - struct id3v2_header_t - { - - uint8_t id[3]; - uint8_t version; - uint8_t revision; - uint8_t footer : 1; - uint8_t experimental : 1; - uint8_t extendedheader : 1; - uint8_t unsynchronization : 1; - uint8_t reserved : 4; - synchsafe32_t size; - } __attribute__((packed)); - - struct id3v2_extended_header_t - { - - synchsafe32_t size; - uint8_t bytes; - uint8_t flags; - } __attribute__((packed)); - - struct id3v2_frame_header_t - { - - uint8_t id[4]; - synchsafe32_t size; - uint8_t flags[2]; - } __attribute__((packed)); -#endif - - static_assert(sizeof(id3v2_header_t) == 10, "id3v2_header_t must be 10 bytes in length"); - static_assert(sizeof(id3v2_extended_header_t) == 6, - "id3v2_extended_header_t must be 6 bytes in length"); - static_assert(sizeof(id3v2_frame_header_t) == 10, - "id3v2_frame_header_t must be 10 bytes in length"); - - // id3v2_frameid_t - // - // An ID3v2 frame identifier - using id3v2_frameid_t = char[4]; - - // frame_t - // - // Internal representation of an ID3v2 frame object - struct frame_t - { - - id3v2_frameid_t id; - uint8_t flags[2]; - size_t size; - std::unique_ptr data; - }; - - // frame_vector_t - // - // Internal representation of the collection of ID3v2 frame objects - using frame_vector_t = std::vector; - - // Instance Constructors - // - id3v2tag(); - id3v2tag(uint8_t const* data, size_t length); - - //----------------------------------------------------------------------- - // Private Member Functions - - // add_text_frame - // - // Adds a text frame - void add_text_frame(id3v2_frameid_t frameid, char const* text, bool append = false); - - // decode_synchsafe - // - // Decodes a 32-bit synchsafe integer - static uint32_t decode_synchsafe(synchsafe32_t const& synchsafe); - - // encode_synchsafe - // - // Encodes a 32-bit synchsafe integer - static void encode_synchsafe(uint32_t val, synchsafe32_t& synchsafe); - - // remove_frames - // - // Removes all instances of a specific frame from the stored state - void remove_frames(id3v2_frameid_t frameid); - - //----------------------------------------------------------------------- - // Member Variables - - frame_vector_t m_frames; // vector<> of tag frames -}; - -//----------------------------------------------------------------------------- - -#pragma warning(pop) - -#endif // __ID3V2TAG_H_ +//----------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef __ID3V2TAG_H_ +#define __ID3V2TAG_H_ +#pragma once + +#include +#include + +#pragma warning(push, 4) + +//--------------------------------------------------------------------------- +// Class id3v2tag +// +// Implements a simple ID3v2 tag generator + +class id3v2tag +{ +public: + // Destructor + // + ~id3v2tag(); + + //----------------------------------------------------------------------- + // Member Functions + + // album + // + // Sets the album (TALB) frame + void album(char const* album); + + // artist + // + // Sets or append an artist (TPE1) frame + void artist(char const* artist, bool append = false); + + // comment + // + // Sets the comment (COMM; no content descriptor) frame + void comment(char const* comment); + + // coverart + // + // Sets the cover art (APIC; type 0x03) frame + void coverart(char const* mimetype, uint8_t const* data, size_t length); + + // create (static) + // + // Factory method, creates a new id3v2tag instance + static std::unique_ptr create(void); + static std::unique_ptr create(uint8_t const* data, size_t length); + + // genre + // + // Sets or append an genre (TCON) frame + void genre(char const* artist, bool append = false); + + // size + // + // Gets the number of bytes required to persist the tag to storage + size_t size(void) const; + + // title + // + // Sets the title (TIT2) frame + void title(char const* title); + + // track + // + // Sets the track (TRCK) frame + void track(char const* track); + + // write + // + // Writes the tag into a memory buffer + bool write(uint8_t* buffer, size_t length) const; + + // year + // + // Sets the year (TYER) frame + void year(char const* year); + +private: + id3v2tag(id3v2tag const&) = delete; + id3v2tag& operator=(id3v2tag const&) = delete; + + // synchsafe32_t + // + // A 32-bit synchsafe integer + using synchsafe32_t = uint8_t[4]; + + // id3v2_header_t + // + // Defines the ID3v2 header structure +#ifdef _WINDOWS +#pragma pack(push, 1) + struct id3v2_header_t + { + + uint8_t id[3]; + uint8_t version; + uint8_t revision; + uint8_t footer : 1; + uint8_t experimental : 1; + uint8_t extendedheader : 1; + uint8_t unsynchronization : 1; + uint8_t reserved : 4; + synchsafe32_t size; + }; + + struct id3v2_extended_header_t + { + + synchsafe32_t size; + uint8_t bytes; + uint8_t flags; + }; + + struct id3v2_frame_header_t + { + + uint8_t id[4]; + synchsafe32_t size; + uint8_t flags[2]; + }; +#pragma pack(pop) +#else + struct id3v2_header_t + { + + uint8_t id[3]; + uint8_t version; + uint8_t revision; + uint8_t footer : 1; + uint8_t experimental : 1; + uint8_t extendedheader : 1; + uint8_t unsynchronization : 1; + uint8_t reserved : 4; + synchsafe32_t size; + } __attribute__((packed)); + + struct id3v2_extended_header_t + { + + synchsafe32_t size; + uint8_t bytes; + uint8_t flags; + } __attribute__((packed)); + + struct id3v2_frame_header_t + { + + uint8_t id[4]; + synchsafe32_t size; + uint8_t flags[2]; + } __attribute__((packed)); +#endif + + static_assert(sizeof(id3v2_header_t) == 10, "id3v2_header_t must be 10 bytes in length"); + static_assert(sizeof(id3v2_extended_header_t) == 6, + "id3v2_extended_header_t must be 6 bytes in length"); + static_assert(sizeof(id3v2_frame_header_t) == 10, + "id3v2_frame_header_t must be 10 bytes in length"); + + // id3v2_frameid_t + // + // An ID3v2 frame identifier + using id3v2_frameid_t = char[4]; + + // frame_t + // + // Internal representation of an ID3v2 frame object + struct frame_t + { + + id3v2_frameid_t id; + uint8_t flags[2]; + size_t size; + std::unique_ptr data; + }; + + // frame_vector_t + // + // Internal representation of the collection of ID3v2 frame objects + using frame_vector_t = std::vector; + + // Instance Constructors + // + id3v2tag(); + id3v2tag(uint8_t const* data, size_t length); + + //----------------------------------------------------------------------- + // Private Member Functions + + // add_text_frame + // + // Adds a text frame + void add_text_frame(id3v2_frameid_t frameid, char const* text, bool append = false); + + // decode_synchsafe + // + // Decodes a 32-bit synchsafe integer + static uint32_t decode_synchsafe(synchsafe32_t const& synchsafe); + + // encode_synchsafe + // + // Encodes a 32-bit synchsafe integer + static void encode_synchsafe(uint32_t val, synchsafe32_t& synchsafe); + + // remove_frames + // + // Removes all instances of a specific frame from the stored state + void remove_frames(id3v2_frameid_t frameid); + + //----------------------------------------------------------------------- + // Member Variables + + frame_vector_t m_frames; // vector<> of tag frames +}; + +//----------------------------------------------------------------------------- + +#pragma warning(pop) + +#endif // __ID3V2TAG_H_ diff --git a/src/libusb_exception.cpp b/src/libusb_exception.cpp index 3227fdb..e596609 100644 --- a/src/libusb_exception.cpp +++ b/src/libusb_exception.cpp @@ -1,88 +1,88 @@ -//--------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//--------------------------------------------------------------------------- - -#include "libusb_exception.h" - -#include "stdafx.h" - -#pragma warning(disable : 4200) -#include - -#pragma warning(push, 4) - -//----------------------------------------------------------------------------- -// libusb_exception Constructor -// -// Arguments: -// -// code - SQLite error code -// message - Message to associate with the exception - -libusb_exception::libusb_exception(int code) -{ - char what[512] = {'\0'}; // Formatted exception string - - snprintf(what, std::extent::value, "%s (%d) : %s", libusb_error_name(code), code, - libusb_strerror(static_cast(code))); - - m_what.assign(what); -} - -//----------------------------------------------------------------------------- -// libusb_exception Copy Constructor - -libusb_exception::libusb_exception(libusb_exception const& rhs) : m_what(rhs.m_what) -{ -} - -//----------------------------------------------------------------------------- -// libusb_exception Move Constructor - -libusb_exception::libusb_exception(libusb_exception&& rhs) : m_what(std::move(rhs.m_what)) -{ -} - -//----------------------------------------------------------------------------- -// libusb_exception char const* conversion operator - -libusb_exception::operator char const*() const -{ - return m_what.c_str(); -} - -//----------------------------------------------------------------------------- -// libusb_exception::what -// -// Gets a pointer to the exception message text -// -// Arguments: -// -// NONE - -char const* libusb_exception::what(void) const noexcept -{ - return m_what.c_str(); -} - -//--------------------------------------------------------------------------- - -#pragma warning(pop) +//--------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//--------------------------------------------------------------------------- + +#include "libusb_exception.h" + +#include "stdafx.h" + +#pragma warning(disable : 4200) +#include + +#pragma warning(push, 4) + +//----------------------------------------------------------------------------- +// libusb_exception Constructor +// +// Arguments: +// +// code - SQLite error code +// message - Message to associate with the exception + +libusb_exception::libusb_exception(int code) +{ + char what[512] = {'\0'}; // Formatted exception string + + snprintf(what, std::extent::value, "%s (%d) : %s", libusb_error_name(code), code, + libusb_strerror(static_cast(code))); + + m_what.assign(what); +} + +//----------------------------------------------------------------------------- +// libusb_exception Copy Constructor + +libusb_exception::libusb_exception(libusb_exception const& rhs) : m_what(rhs.m_what) +{ +} + +//----------------------------------------------------------------------------- +// libusb_exception Move Constructor + +libusb_exception::libusb_exception(libusb_exception&& rhs) : m_what(std::move(rhs.m_what)) +{ +} + +//----------------------------------------------------------------------------- +// libusb_exception char const* conversion operator + +libusb_exception::operator char const*() const +{ + return m_what.c_str(); +} + +//----------------------------------------------------------------------------- +// libusb_exception::what +// +// Gets a pointer to the exception message text +// +// Arguments: +// +// NONE + +char const* libusb_exception::what(void) const noexcept +{ + return m_what.c_str(); +} + +//--------------------------------------------------------------------------- + +#pragma warning(pop) diff --git a/src/libusb_exception.h b/src/libusb_exception.h index 3ad0929..dc4a666 100644 --- a/src/libusb_exception.h +++ b/src/libusb_exception.h @@ -1,75 +1,75 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//----------------------------------------------------------------------------- - -#ifndef __LIBUSB_EXCEPTION_H_ -#define __LIBUSB_EXCEPTION_H_ -#pragma once - -#include -#include - -#pragma warning(push, 4) - -//----------------------------------------------------------------------------- -// Class libusb_exception -// -// std::exception used to wrap libusb error conditions - -class libusb_exception : public std::exception -{ -public: - // Instance Constructors - // - libusb_exception(int code); - - // Copy Constructor - // - libusb_exception(libusb_exception const& rhs); - - // Move Constructor - // - libusb_exception(libusb_exception&& rhs); - - // char const* conversion operator - // - operator char const*() const; - - //------------------------------------------------------------------------- - // Member Functions - - // what (std::exception) - // - // Gets a pointer to the exception message text - virtual char const* what(void) const noexcept override; - -private: - //------------------------------------------------------------------------- - // Member Variables - - std::string m_what; // libusb error message -}; - -//----------------------------------------------------------------------------- - -#pragma warning(pop) - -#endif // __LIBUSB_EXCEPTION_H_ +//----------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef __LIBUSB_EXCEPTION_H_ +#define __LIBUSB_EXCEPTION_H_ +#pragma once + +#include +#include + +#pragma warning(push, 4) + +//----------------------------------------------------------------------------- +// Class libusb_exception +// +// std::exception used to wrap libusb error conditions + +class libusb_exception : public std::exception +{ +public: + // Instance Constructors + // + libusb_exception(int code); + + // Copy Constructor + // + libusb_exception(libusb_exception const& rhs); + + // Move Constructor + // + libusb_exception(libusb_exception&& rhs); + + // char const* conversion operator + // + operator char const*() const; + + //------------------------------------------------------------------------- + // Member Functions + + // what (std::exception) + // + // Gets a pointer to the exception message text + virtual char const* what(void) const noexcept override; + +private: + //------------------------------------------------------------------------- + // Member Variables + + std::string m_what; // libusb error message +}; + +//----------------------------------------------------------------------------- + +#pragma warning(pop) + +#endif // __LIBUSB_EXCEPTION_H_ diff --git a/src/muxscanner.h b/src/muxscanner.h index 2dd5851..088b81e 100644 --- a/src/muxscanner.h +++ b/src/muxscanner.h @@ -1,94 +1,94 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//----------------------------------------------------------------------------- - -#ifndef __MUXSCANNER_H_ -#define __MUXSCANNER_H_ -#pragma once - -#include "props.h" - -#include -#include -#include - -#pragma warning(push, 4) - -//--------------------------------------------------------------------------- -// Class muxscanner -// -// Defines the interface required for providing muxscanner/ensemble information - -class muxscanner -{ -public: - // Constructor / Destructor - // - muxscanner() {} - virtual ~muxscanner() {} - - //----------------------------------------------------------------------- - // Type Declarations - - // subchannel - // - // Structure used to report subchannel properties - struct subchannel - { - - uint32_t number; // Subchannel number - std::string name; // Subchannel name - }; - - // multiplex - // - // Structure used to report the multiplex properties - struct multiplex - { - - bool sync; // Sync (lock) flag - std::string name; // Multiplex name - std::vector subchannels; // Multiplex subchannels - }; - - // callback - // - // Callback function invoked when the multiplex properties have changed - using callback = std::function; - - //----------------------------------------------------------------------- - // Member Functions - - // inputsamples - // - // Pipes input samples into the muliplex scanner - virtual void inputsamples(uint8_t const* samples, size_t length) = 0; - -private: - muxscanner(muxscanner const&) = delete; - muxscanner& operator=(muxscanner const&) = delete; -}; - -//----------------------------------------------------------------------------- - -#pragma warning(pop) - -#endif // __MUXSCANNER_H_ +//----------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef __MUXSCANNER_H_ +#define __MUXSCANNER_H_ +#pragma once + +#include "props.h" + +#include +#include +#include + +#pragma warning(push, 4) + +//--------------------------------------------------------------------------- +// Class muxscanner +// +// Defines the interface required for providing muxscanner/ensemble information + +class muxscanner +{ +public: + // Constructor / Destructor + // + muxscanner() {} + virtual ~muxscanner() {} + + //----------------------------------------------------------------------- + // Type Declarations + + // subchannel + // + // Structure used to report subchannel properties + struct subchannel + { + + uint32_t number; // Subchannel number + std::string name; // Subchannel name + }; + + // multiplex + // + // Structure used to report the multiplex properties + struct multiplex + { + + bool sync; // Sync (lock) flag + std::string name; // Multiplex name + std::vector subchannels; // Multiplex subchannels + }; + + // callback + // + // Callback function invoked when the multiplex properties have changed + using callback = std::function; + + //----------------------------------------------------------------------- + // Member Functions + + // inputsamples + // + // Pipes input samples into the muliplex scanner + virtual void inputsamples(uint8_t const* samples, size_t length) = 0; + +private: + muxscanner(muxscanner const&) = delete; + muxscanner& operator=(muxscanner const&) = delete; +}; + +//----------------------------------------------------------------------------- + +#pragma warning(pop) + +#endif // __MUXSCANNER_H_ diff --git a/src/rdsdecoder.cpp b/src/rdsdecoder.cpp index 2608154..2478570 100644 --- a/src/rdsdecoder.cpp +++ b/src/rdsdecoder.cpp @@ -1,807 +1,807 @@ -//--------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//--------------------------------------------------------------------------- - -#include "rdsdecoder.h" - -#include "fmdsp/rbdsconstants.h" -#include "stdafx.h" - -#include - -#pragma warning(push, 4) - -//--------------------------------------------------------------------------- -// rdsdecoder Constructor -// -// Arguments: -// -// isrbds - Flag if input will be RBDS (North America) or RDS - -rdsdecoder::rdsdecoder(bool isrbds) : m_isrbds(isrbds) -{ - m_ps_data.fill(0x00); // Initialize PS buffer - m_rt_data.fill(0x00); // Initialize RT buffer - m_rbds_callsign.fill(0x00); // Initialize callsign buffer -} - -//--------------------------------------------------------------------------- -// rdsdecoder Destructor - -rdsdecoder::~rdsdecoder() -{ -} - -//--------------------------------------------------------------------------- -// rdsdecoder::decode_applicationidentification -// -// Decodes Group Type 3A - Application Identification -// -// Arguments: -// -// rdsgroiup - RDS group to be processed - -void rdsdecoder::decode_applicationidentification(tRDS_GROUPS const& rdsgroup) -{ - // Determine if this is Group A or Group B data - bool const groupa = ((rdsgroup.BlockB & 0x0800) == 0x0000); - - if (groupa) - { - - // Determine what Open Data Application(s) are present by checking - // the Application ID (AID) against known values - // - // (https://www.nrscstandards.org/committees/dsm/archive/rds-oda-aids.pdf) - switch (rdsgroup.BlockD) - { - - // 0x4BD7: RadioText+ (RT+) - case 0x4BD7: - m_oda_rtplus = true; - m_rtplus_group = (rdsgroup.BlockB >> 1) & 0x0F; - m_rtplus_group_ab = rdsgroup.BlockB & 0x01; - break; - - // 0xCD46: Traffic Message Channel (RDS-TMC) - case 0xCD46: - m_oda_rdstmc = true; - break; - } - } -} - -//--------------------------------------------------------------------------- -// rdsdecoder::decode_basictuning -// -// Decodes Group Type 0A and 0B - Basic Tuning and swithing information -// -// Arguments: -// -// rdsgroup - RDS group to be processed - -void rdsdecoder::decode_basictuning(tRDS_GROUPS const& rdsgroup) -{ - uint8_t const ps_codebits = rdsgroup.BlockB & 0x03; - - m_ps_data[ps_codebits * 2] = (rdsgroup.BlockD >> 8) & 0xFF; - m_ps_data[ps_codebits * 2 + 1] = rdsgroup.BlockD & 0xFF; - - // Accumulate segments until all 4 (0xF) have been received - m_ps_ready |= (0x01 << ps_codebits); - if (m_ps_ready == 0x0F) - { - - // UECP_MEC_PS - // - struct uecp_data_frame frame = {}; - struct uecp_message* message = &frame.msg; - - message->mec = UECP_MEC_PS; - message->dsn = UECP_MSG_DSN_CURRENT_SET; - message->psn = UECP_MSG_PSN_MAIN; - - // Kodi expects the 8 characters to start at the address of mel_len - // when processing UECP_MEC_PS - uint8_t* mel_data = &message->mel_len; - memcpy(mel_data, &m_ps_data[0], m_ps_data.size()); - - frame.seq = UECP_DF_SEQ_DISABLED; - frame.msg_len = 3 + 8; // mec, dsn, psn + mel_data[8] - - // Convert the UECP data frame into a packet and queue it up - m_uecp_packets.emplace(uecp_create_data_packet(frame)); - - // Reset the segment accumulator back to zero - m_ps_ready = 0x00; - } -} - -//--------------------------------------------------------------------------- -// rdsdecoder::decode_programidentification -// -// Decodes Program Identification (PI) -// -// Arguments: -// -// rdsgroup - RDS group to be processed - -void rdsdecoder::decode_programidentification(tRDS_GROUPS const& rdsgroup) -{ - uint16_t const pi = rdsgroup.BlockA; - - // Indicate a change to the Program Identification flags - if (pi != m_pi) - { - - // UECP_MEC_PI - // - struct uecp_data_frame frame = {}; - struct uecp_message* message = &frame.msg; - - message->mec = UECP_MEC_PI; - message->dsn = UECP_MSG_DSN_CURRENT_SET; - message->psn = UECP_MSG_PSN_MAIN; - - // Kodi expects a single word for PI at the address of mel_len - *reinterpret_cast(&message->mel_len) = pi & 0xFF; - *reinterpret_cast(&message->mel_data[0]) = (pi >> 8) & 0xFF; - - frame.seq = UECP_DF_SEQ_DISABLED; - frame.msg_len = 3 + 2; // mec, dsn, psn + mel_data[2] - - // Convert the UECP data frame into a packet and queue it up - m_uecp_packets.emplace(uecp_create_data_packet(frame)); - - // Save the current PI flags - m_pi = pi; - } -} - -//--------------------------------------------------------------------------- -// rdsdecoder::decode_programtype -// -// Decodes Program Type (PTY) -// -// Arguments: -// -// rdsgroup - RDS group to be processed - -void rdsdecoder::decode_programtype(tRDS_GROUPS const& rdsgroup) -{ - uint8_t const pty = (rdsgroup.BlockB >> 5) & 0x1F; - - // Indicate a change to the Program Type flags - if (pty != m_pty) - { - - // UECP_MEC_PTY - // - struct uecp_data_frame frame = {}; - struct uecp_message* message = &frame.msg; - - message->mec = UECP_MEC_PTY; - message->dsn = UECP_MSG_DSN_CURRENT_SET; - message->psn = UECP_MSG_PSN_MAIN; - - // Kodi expects a single byte for PTY at the address of mel_len - *reinterpret_cast(&message->mel_len) = pty; - - frame.seq = UECP_DF_SEQ_DISABLED; - frame.msg_len = 3 + 1; // mec, dsn, psn + mel_data[1] - - // Convert the UECP data frame into a packet and queue it up - m_uecp_packets.emplace(uecp_create_data_packet(frame)); - - // Save the current PTY flags - m_pty = pty; - } -} - -//--------------------------------------------------------------------------- -// rdsdecoder::decode_radiotextplus -// -// Decodes the RadioText+ (RT+) ODA -// -// Arguments: -// -// rdsgroup - RDS group to be processed - -void rdsdecoder::decode_radiotextplus(tRDS_GROUPS const& rdsgroup) -{ - // Determine if the group A/B flag matches that set for the RT+ application - if (m_rtplus_group_ab == ((rdsgroup.BlockB >> 11) & 0x01)) - { - - // UECP_ODA_DATA - // - struct uecp_data_frame frame = {}; - struct uecp_message* message = &frame.msg; - - // Kodi treats UECP_ODA_DATA as a custom data packet - message->mec = UECP_ODA_DATA; - message->dsn = 8; // ODA data length - message->psn = 0x4B; // High byte of ODA AID (0x4BD7) - message->mel_len = 0xD7; // Low byte of ODA AID (0x4BD7) - - // Pack the BlockB, BlockC, and BlockD data from the RDS group into the packet - message->mel_data[0] = (rdsgroup.BlockB >> 8) & 0xFF; - message->mel_data[1] = rdsgroup.BlockB & 0xFF; - message->mel_data[2] = (rdsgroup.BlockC >> 8) & 0xFF; - message->mel_data[3] = rdsgroup.BlockC & 0xFF; - message->mel_data[4] = (rdsgroup.BlockD >> 8) & 0xFF; - message->mel_data[5] = rdsgroup.BlockD & 0xFF; - - frame.seq = UECP_DF_SEQ_DISABLED; - frame.msg_len = 4 + 6; // mec, dsn, psn, mel_data + 6 bytes - - // Convert the UECP data frame into a packet and queue it up - m_uecp_packets.emplace(uecp_create_data_packet(frame)); - } -} - -//--------------------------------------------------------------------------- -// rdsdecoder::decode_radiotext -// -// Decodes Group Type 2A and 2B - RadioText -// -// Arguments: -// -// rdsgroup - RDS group to be processed - -void rdsdecoder::decode_radiotext(tRDS_GROUPS const& rdsgroup) -{ - bool hascr = false; // Flag if carriage return detected - - // Get the text segment address and A/B indicators from the data - uint8_t textsegmentaddress = rdsgroup.BlockB & 0x000F; - uint8_t const ab = (rdsgroup.BlockB >> 4) & 0x01; - - // Set the initial A/B flag the first time it's been seen - if (!m_rt_init) - { - m_rt_ab = ab; - m_rt_init = true; - } - - // Clear any existing radio text when the A/B flag changes - if (ab != m_rt_ab) - { - - m_rt_ab = ab; // Toggle A/B - m_rt_data.fill(0x00); // Clear existing RT data - m_rt_ready = 0x0000; // Clear existing segment flags - } - - // Determine if this is Group A or Group B data - bool const groupa = ((rdsgroup.BlockB & 0x0800) == 0x0000); - - // Group A contains two RadioText segments in block C and D - if (groupa) - { - - size_t offset = static_cast(textsegmentaddress << 2); - m_rt_data[offset + 0] = (rdsgroup.BlockC >> 8) & 0xFF; - m_rt_data[offset + 1] = rdsgroup.BlockC & 0xFF; - m_rt_data[offset + 2] = (rdsgroup.BlockD >> 8) & 0xFF; - m_rt_data[offset + 3] = rdsgroup.BlockD & 0xFF; - - // Check if a carriage return has been sent in this text segment - hascr = ((m_rt_data[offset + 0] == 0x0D) || (m_rt_data[offset + 1] == 0x0D) || - (m_rt_data[offset + 2] == 0x0D) || (m_rt_data[offset + 3] == 0x0D)); - } - - // Group B contains one RadioText segment in block D - else - { - - size_t offset = static_cast(textsegmentaddress << 1); - m_rt_data[offset + 0] = (rdsgroup.BlockD >> 8) & 0xFF; - m_rt_data[offset + 1] = rdsgroup.BlockD & 0xFF; - - // Check if a carriage return has been sent in this text segment - hascr = ((m_rt_data[offset + 0] == 0x0D) || (m_rt_data[offset + 1] == 0x0D)); - } - - // Indicate that this segment has been received, and if a CR was detected flag - // all remaining text segments as received (they're not going to come anyway) - m_rt_ready |= (0x01 << textsegmentaddress); - while ((hascr) && (++textsegmentaddress < 16)) - { - - // Clear any RT information that may have been previously set - size_t offset = static_cast(textsegmentaddress << 2); - m_rt_data[offset + 0] = 0x00; - m_rt_data[offset + 1] = 0x00; - if (groupa) - m_rt_data[offset + 2] = 0x00; - if (groupa) - m_rt_data[offset + 3] = 0x00; - - // Flag this segment as ready in the bitmask - m_rt_ready |= (0x01 << textsegmentaddress); - } - - // The RT information is ready to be sent if all 16 segments have been retrieved - // for a Group A signal, or the first 8 segments of a Group B signal - if ((groupa) ? m_rt_ready == 0xFFFF : ((m_rt_ready & 0x00FF) == 0x00FF)) - { - - // UECP_MEC_RT - // - struct uecp_data_frame frame = {}; - struct uecp_message* message = &frame.msg; - - message->mec = UECP_MEC_RT; - message->dsn = UECP_MSG_DSN_CURRENT_SET; - message->psn = UECP_MSG_PSN_MAIN; - - message->mel_len = 0x01; - message->mel_data[0] = m_rt_ab; - - for (size_t index = 0; index < m_rt_data.size(); index++) - { - - if (m_rt_data[index] == 0x00) - break; - message->mel_data[message->mel_len++] = m_rt_data[index]; - } - - frame.seq = UECP_DF_SEQ_DISABLED; - frame.msg_len = 4 + message->mel_len; - - // Convert the UECP data frame into a packet and queue it up - m_uecp_packets.emplace(uecp_create_data_packet(frame)); - - // Reset the segment accumulator back to zero - m_rt_ready = 0x0000; - } -} - -//--------------------------------------------------------------------------- -// rdsdecoder::decode_rbds_programidentification -// -// Decodes RBDS Program Identification (PI) -// -// Arguments: -// -// rdsgroup - RDS group to be processed - -void rdsdecoder::decode_rbds_programidentification(tRDS_GROUPS const& rdsgroup) -{ - uint16_t pi = rdsgroup.BlockA; - - // Indicate a change to the Program Identification flags - if (pi != m_rbds_pi) - { - - uint8_t countrycode = 0xA0; // US - - m_rbds_pi = pi; - m_rbds_callsign.fill('\0'); - m_rbds_nationalcode.clear(); - - // SPECIAL CASE: AFxx -> xx00 - // - if ((pi & 0xAF00) == 0xAF00) - pi <<= 8; - - // SPECIAL CASE: Axxx -> x0xx - // - if ((pi & 0xA000) == 0xA000) - pi = (((pi & 0xF00) << 4) | (pi & 0xFF)); - - // NATIONALLY/REGIONALLY-LINKED RADIO STATION CODES - // - if ((pi & 0xB000) == 0xB000) - { - - // The low byte of these PI codes defines what nationally/regionally linked - // station code should be returned instead of a station call sign - switch (pi & 0xFF) - { - - case 0x0001: - m_rbds_nationalcode = "NPR-1"; - break; - case 0x0002: - m_rbds_nationalcode = "CBC Radio One"; - break; - case 0x0003: - m_rbds_nationalcode = "CBC Radio Two"; - break; - case 0x0004: - m_rbds_nationalcode = "CBC Premiere Chaine"; - break; - case 0x0005: - m_rbds_nationalcode = "CBC Espace Musique"; - break; - case 0x0006: - m_rbds_nationalcode = "CBC"; - break; - case 0x0007: - m_rbds_nationalcode = "CBC"; - break; - case 0x0008: - m_rbds_nationalcode = "CBC"; - break; - case 0x0009: - m_rbds_nationalcode = "CBC"; - break; - case 0x000A: - m_rbds_nationalcode = "NPR-2"; - break; - case 0x000B: - m_rbds_nationalcode = "NPR-3"; - break; - case 0x000C: - m_rbds_nationalcode = "NPR-4"; - break; - case 0x000D: - m_rbds_nationalcode = "NPR-5"; - break; - case 0x000E: - m_rbds_nationalcode = "NPR-6"; - break; - } - - // If a Canadian Nationally/Regionally linked code was set switch to Canada - if (!m_rbds_nationalcode.empty() && (m_rbds_nationalcode[0] == 'C')) - countrycode = 0xA1; - } - - // USA 3-LETTER-ONLY (ref: NRSC-4-B 04.2011 Table D.7) - // - else if ((pi >= 0x9950) && (pi <= 0x9EFF)) - { - - // The 3-letter only callsigns are static and represented in a lookup table - for (auto const iterator : CALL3TABLE) - { - - if (iterator.pi == pi) - { - - m_rbds_callsign[0] = iterator.csign[0]; - m_rbds_callsign[1] = iterator.csign[1]; - m_rbds_callsign[2] = iterator.csign[2]; - break; - } - } - } - - // USA EAST (Wxxx) - // - else if ((pi >= 21672) && (pi <= 39247)) - { - - uint16_t char1 = (pi - 21672) / 676; - uint16_t char2 = ((pi - 21672) - (char1 * 676)) / 26; - uint16_t char3 = ((pi - 21672) - (char1 * 676) - (char2 * 26)); - - m_rbds_callsign[0] = 'W'; - m_rbds_callsign[1] = static_cast(static_cast('A') + char1); - m_rbds_callsign[2] = static_cast(static_cast('A') + char2); - m_rbds_callsign[3] = static_cast(static_cast('A') + char3); - } - - // USA WEST (Kxxx) - // - else if ((pi >= 4096) && (pi <= 21671)) - { - - uint16_t char1 = (pi - 4096) / 676; - uint16_t char2 = ((pi - 4096) - (char1 * 676)) / 26; - uint16_t char3 = ((pi - 4096) - (char1 * 676) - (char2 * 26)); - - m_rbds_callsign[0] = 'K'; - m_rbds_callsign[1] = static_cast(static_cast('A') + char1); - m_rbds_callsign[2] = static_cast(static_cast('A') + char2); - m_rbds_callsign[3] = static_cast(static_cast('A') + char3); - } - - // CANADA - // - // Reverse engineered from "Program information codes for radio broadcasting stations" - // (https://www.ic.gc.ca/eic/site/smt-gst.nsf/eng/h_sf08741.html) - // - else if ((pi & 0xC000) == 0xC000) - { - - countrycode = 0xA1; // CA - - // Determine the offset and increment values from the PI code - uint16_t offset = ((pi - 0xC000) - 257) / 255; - uint16_t increment = (pi - 0xC000) - offset; - - // Calculate the individual character code values (interpretation differs for each) - uint16_t char1 = (increment - 257) / (26 * 27); - uint16_t char2 = ((increment - 257) - (char1 * (26 * 27))) / 27; - uint16_t char3 = ((increment - 257) - (char1 * (26 * 27))) - (char2 * 27); - - // Convert the second character of the call sign first as there is a small range of - // documented valid characters available; anything out of range should be ignored - if (char1 == 0) - m_rbds_callsign[1] = 'F'; - else if (char1 == 1) - m_rbds_callsign[1] = 'H'; - else if (char1 == 2) - m_rbds_callsign[1] = 'I'; - else if (char1 == 3) - m_rbds_callsign[1] = 'J'; - else if (char1 == 4) - m_rbds_callsign[1] = 'K'; - - // Only fill in the rest of the callsign if char1 was in the valid range - if (m_rbds_callsign[1] != '\0') - { - - // The first character is always 'C' - m_rbds_callsign[0] = 'C'; - - // The third character is always present and is zero-based from 'A' - m_rbds_callsign[2] = static_cast(static_cast('A') + char2); - - // The fourth character is optional and one-based from 'A' - if (char3 != 0) - m_rbds_callsign[3] = static_cast(static_cast('A') + (char3 - 1)); - } - } - - // MEXICO - // - else if ((pi & 0xF000) == 0xF000) - { - - countrycode = 0xA5; // MX - - // TODO - I need some manner of reference material here - } - - // Generate a couple fake UECP packets anytime the PI changes to allow Kodi - // to get the internals right for North American broadcasts with RBDS - struct uecp_data_frame frame = {}; - struct uecp_message* message = &frame.msg; - - // UECP_MEC_PI - // - message->mec = UECP_MEC_PI; - message->dsn = UECP_MSG_DSN_CURRENT_SET; - message->psn = UECP_MSG_PSN_MAIN; - - // Kodi expects a single word for PI at the address of mel_len; for RBDS - // hard-code it to 0xB000 which points to this row in the lookup data which - // has all three required country codes "US", "CA" and "MX" and can be properly - // set via the UECP_EPP_TM_INFO packet by specifying a PSN of 0xA0 for "US", - // 0xA1 for "CA", and 0xA5 for "MX": - // - // {"US","CA","BR","DO","LC","MX","__"}, // B - // - *reinterpret_cast(&message->mel_len) = 0xB0; - *reinterpret_cast(&message->mel_data[0]) = 0x00; - - frame.seq = UECP_DF_SEQ_DISABLED; - frame.msg_len = 3 + 2; // mec, dsn, psn + mel_data[2] - - // Convert the UECP data frame into a packet and queue it up - m_uecp_packets.emplace(uecp_create_data_packet(frame)); - - // UECP_EPP_TM_INFO - // - message->mec = UECP_EPP_TM_INFO; - message->dsn = UECP_MSG_DSN_CURRENT_SET; - message->psn = countrycode; - - frame.seq = UECP_DF_SEQ_DISABLED; - frame.msg_len = 3; // mec, dsn, psn - - // Convert the UECP data frame into a packet and queue it up - m_uecp_packets.emplace(uecp_create_data_packet(frame)); - } -} - -//--------------------------------------------------------------------------- -// rdsdecoder::decode_rdsgroup -// -// Decodes the next RDS group -// -// Arguments: -// -// rdsgroup - Next RDS group to be processed - -void rdsdecoder::decode_rdsgroup(tRDS_GROUPS const& rdsgroup) -{ - // Ignore spurious RDS packets that contain no data - // todo: figure out how this happens; could be a bug in the FM DSP - if ((rdsgroup.BlockA == 0x0000) && (rdsgroup.BlockB == 0x0000) && (rdsgroup.BlockC == 0x0000) && - (rdsgroup.BlockD == 0x0000)) - return; - - // Determine the group type code - uint8_t grouptypecode = (rdsgroup.BlockB >> 12) & 0x0F; - - // Program Identification - // - if (m_isrbds) - decode_rbds_programidentification(rdsgroup); - else - decode_programidentification(rdsgroup); - - // Program Type - // - decode_programtype(rdsgroup); - - // Group Type 0: Basic Tuning and switching information - if (grouptypecode == 0) - decode_basictuning(rdsgroup); - - // Group Type 1: Slow Labelling Codes - else if (grouptypecode == 1) - decode_slowlabellingcodes(rdsgroup); - - // Group Type 2: RadioText - else if (grouptypecode == 2) - decode_radiotext(rdsgroup); - - // Group Type 3: Application Identification - else if (grouptypecode == 3) - decode_applicationidentification(rdsgroup); - - // RadioText+ (RT+) Open Data Application - if (m_oda_rtplus && grouptypecode == m_rtplus_group) - decode_radiotextplus(rdsgroup); -} - -//--------------------------------------------------------------------------- -// rdsdecoder::decode_slowlabellingcodes -// -// Decodes Group 1A - Slow Labelling Codes -// -// Arguments: -// -// rdsgroup - RDS group to be processed - -void rdsdecoder::decode_slowlabellingcodes(tRDS_GROUPS const& rdsgroup) -{ - // Determine if this is Group A or Group B data - bool const groupa = ((rdsgroup.BlockB & 0x0800) == 0x0000); - - if (groupa) - { - - // UECP_MEC_SLOW_LABEL_CODES - // - struct uecp_data_frame frame = {}; - struct uecp_message* message = &frame.msg; - - message->mec = UECP_MEC_SLOW_LABEL_CODES; - message->dsn = UECP_MSG_DSN_CURRENT_SET; - //message->psn = UECP_MSG_PSN_MAIN; - - // For whatever reason, Kodi expects the high byte of the data to - // be in the message PSN field -- could be a bug in Kodi, but whatever - message->psn = ((rdsgroup.BlockC >> 8) & 0xFF); - *reinterpret_cast(&message->mel_len) = (rdsgroup.BlockC & 0xFF); - - frame.seq = UECP_DF_SEQ_DISABLED; - frame.msg_len = 3 + 1; // mec, dsn, psn + mel_data[1] - - // Convert the UECP data frame into a packet and queue it up - m_uecp_packets.emplace(uecp_create_data_packet(frame)); - } -} - -//--------------------------------------------------------------------------- -// rdsdecoder::get_rdbs_callsign -// -// Retrieves the RBDS call sign (if present) -// -// Arguments: -// -// NONE - -std::string rdsdecoder::get_rbds_callsign(void) const -{ - // If this is a nationally/regionally linked station return that string - if (!m_rbds_nationalcode.empty()) - return m_rbds_nationalcode; - - // The callsign may not have every letter assigned, trim any NULLs at the end - std::string callsign = std::string(m_rbds_callsign.begin(), m_rbds_callsign.end()); - auto trimpos = callsign.find('\0'); - if (trimpos != std::string::npos) - callsign.erase(trimpos); - - // If this wasn't a nationally/regionally-linked station, append the "-FM" suffix - return callsign + "-FM"; -} - -//--------------------------------------------------------------------------- -// rdsdecoder::has_radiotextplus -// -// Flag indicating that the RadioText+ (RT+) ODA is present -// -// Arguments: -// -// NONE - -bool rdsdecoder::has_radiotextplus(void) const -{ - return m_oda_rtplus; -} - -//--------------------------------------------------------------------------- -// rdsdecoder::has_rbds_callsign -// -// Flag indicating that the RDBS call sign has been decoded -// -// Arguments: -// -// NONE - -bool rdsdecoder::has_rbds_callsign(void) const -{ - // SPECIAL CASE: If the first nibble of the RBDS PI code is 1 the callsign data - // cannot be decoded if the RDS-TMC ODA is also present (NRSC-4-B, section D.4.7) - if (((m_rbds_pi & 0x1000) == 0x1000) && (m_oda_rdstmc)) - return false; - - return (!m_rbds_nationalcode.empty()) || (m_rbds_callsign[0] != '\0'); -} - -//--------------------------------------------------------------------------- -// rdsdecoder::has_rdstmc -// -// Flag indicating that the Traffic Message Channel (RDS-TMC) ODA is present -// -// Arguments: -// -// NONE - -bool rdsdecoder::has_rdstmc(void) const -{ - return m_oda_rdstmc; -} - -//--------------------------------------------------------------------------- -// rdsdecoder::pop_uecp_data_packet -// -// Pops the topmost UECP data packet out of the packet queue -// -// Arguments: -// -// packet - On success, contains the UECP packet data - -bool rdsdecoder::pop_uecp_data_packet(uecp_data_packet& packet) -{ - if (m_uecp_packets.empty()) - return false; - - // Swap the front packet from the top of the queue and pop it off - m_uecp_packets.front().swap(packet); - m_uecp_packets.pop(); - - return true; -} - -//--------------------------------------------------------------------------- - -#pragma warning(pop) +//--------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//--------------------------------------------------------------------------- + +#include "rdsdecoder.h" + +#include "fmdsp/rbdsconstants.h" +#include "stdafx.h" + +#include + +#pragma warning(push, 4) + +//--------------------------------------------------------------------------- +// rdsdecoder Constructor +// +// Arguments: +// +// isrbds - Flag if input will be RBDS (North America) or RDS + +rdsdecoder::rdsdecoder(bool isrbds) : m_isrbds(isrbds) +{ + m_ps_data.fill(0x00); // Initialize PS buffer + m_rt_data.fill(0x00); // Initialize RT buffer + m_rbds_callsign.fill(0x00); // Initialize callsign buffer +} + +//--------------------------------------------------------------------------- +// rdsdecoder Destructor + +rdsdecoder::~rdsdecoder() +{ +} + +//--------------------------------------------------------------------------- +// rdsdecoder::decode_applicationidentification +// +// Decodes Group Type 3A - Application Identification +// +// Arguments: +// +// rdsgroiup - RDS group to be processed + +void rdsdecoder::decode_applicationidentification(tRDS_GROUPS const& rdsgroup) +{ + // Determine if this is Group A or Group B data + bool const groupa = ((rdsgroup.BlockB & 0x0800) == 0x0000); + + if (groupa) + { + + // Determine what Open Data Application(s) are present by checking + // the Application ID (AID) against known values + // + // (https://www.nrscstandards.org/committees/dsm/archive/rds-oda-aids.pdf) + switch (rdsgroup.BlockD) + { + + // 0x4BD7: RadioText+ (RT+) + case 0x4BD7: + m_oda_rtplus = true; + m_rtplus_group = (rdsgroup.BlockB >> 1) & 0x0F; + m_rtplus_group_ab = rdsgroup.BlockB & 0x01; + break; + + // 0xCD46: Traffic Message Channel (RDS-TMC) + case 0xCD46: + m_oda_rdstmc = true; + break; + } + } +} + +//--------------------------------------------------------------------------- +// rdsdecoder::decode_basictuning +// +// Decodes Group Type 0A and 0B - Basic Tuning and swithing information +// +// Arguments: +// +// rdsgroup - RDS group to be processed + +void rdsdecoder::decode_basictuning(tRDS_GROUPS const& rdsgroup) +{ + uint8_t const ps_codebits = rdsgroup.BlockB & 0x03; + + m_ps_data[ps_codebits * 2] = (rdsgroup.BlockD >> 8) & 0xFF; + m_ps_data[ps_codebits * 2 + 1] = rdsgroup.BlockD & 0xFF; + + // Accumulate segments until all 4 (0xF) have been received + m_ps_ready |= (0x01 << ps_codebits); + if (m_ps_ready == 0x0F) + { + + // UECP_MEC_PS + // + struct uecp_data_frame frame = {}; + struct uecp_message* message = &frame.msg; + + message->mec = UECP_MEC_PS; + message->dsn = UECP_MSG_DSN_CURRENT_SET; + message->psn = UECP_MSG_PSN_MAIN; + + // Kodi expects the 8 characters to start at the address of mel_len + // when processing UECP_MEC_PS + uint8_t* mel_data = &message->mel_len; + memcpy(mel_data, &m_ps_data[0], m_ps_data.size()); + + frame.seq = UECP_DF_SEQ_DISABLED; + frame.msg_len = 3 + 8; // mec, dsn, psn + mel_data[8] + + // Convert the UECP data frame into a packet and queue it up + m_uecp_packets.emplace(uecp_create_data_packet(frame)); + + // Reset the segment accumulator back to zero + m_ps_ready = 0x00; + } +} + +//--------------------------------------------------------------------------- +// rdsdecoder::decode_programidentification +// +// Decodes Program Identification (PI) +// +// Arguments: +// +// rdsgroup - RDS group to be processed + +void rdsdecoder::decode_programidentification(tRDS_GROUPS const& rdsgroup) +{ + uint16_t const pi = rdsgroup.BlockA; + + // Indicate a change to the Program Identification flags + if (pi != m_pi) + { + + // UECP_MEC_PI + // + struct uecp_data_frame frame = {}; + struct uecp_message* message = &frame.msg; + + message->mec = UECP_MEC_PI; + message->dsn = UECP_MSG_DSN_CURRENT_SET; + message->psn = UECP_MSG_PSN_MAIN; + + // Kodi expects a single word for PI at the address of mel_len + *reinterpret_cast(&message->mel_len) = pi & 0xFF; + *reinterpret_cast(&message->mel_data[0]) = (pi >> 8) & 0xFF; + + frame.seq = UECP_DF_SEQ_DISABLED; + frame.msg_len = 3 + 2; // mec, dsn, psn + mel_data[2] + + // Convert the UECP data frame into a packet and queue it up + m_uecp_packets.emplace(uecp_create_data_packet(frame)); + + // Save the current PI flags + m_pi = pi; + } +} + +//--------------------------------------------------------------------------- +// rdsdecoder::decode_programtype +// +// Decodes Program Type (PTY) +// +// Arguments: +// +// rdsgroup - RDS group to be processed + +void rdsdecoder::decode_programtype(tRDS_GROUPS const& rdsgroup) +{ + uint8_t const pty = (rdsgroup.BlockB >> 5) & 0x1F; + + // Indicate a change to the Program Type flags + if (pty != m_pty) + { + + // UECP_MEC_PTY + // + struct uecp_data_frame frame = {}; + struct uecp_message* message = &frame.msg; + + message->mec = UECP_MEC_PTY; + message->dsn = UECP_MSG_DSN_CURRENT_SET; + message->psn = UECP_MSG_PSN_MAIN; + + // Kodi expects a single byte for PTY at the address of mel_len + *reinterpret_cast(&message->mel_len) = pty; + + frame.seq = UECP_DF_SEQ_DISABLED; + frame.msg_len = 3 + 1; // mec, dsn, psn + mel_data[1] + + // Convert the UECP data frame into a packet and queue it up + m_uecp_packets.emplace(uecp_create_data_packet(frame)); + + // Save the current PTY flags + m_pty = pty; + } +} + +//--------------------------------------------------------------------------- +// rdsdecoder::decode_radiotextplus +// +// Decodes the RadioText+ (RT+) ODA +// +// Arguments: +// +// rdsgroup - RDS group to be processed + +void rdsdecoder::decode_radiotextplus(tRDS_GROUPS const& rdsgroup) +{ + // Determine if the group A/B flag matches that set for the RT+ application + if (m_rtplus_group_ab == ((rdsgroup.BlockB >> 11) & 0x01)) + { + + // UECP_ODA_DATA + // + struct uecp_data_frame frame = {}; + struct uecp_message* message = &frame.msg; + + // Kodi treats UECP_ODA_DATA as a custom data packet + message->mec = UECP_ODA_DATA; + message->dsn = 8; // ODA data length + message->psn = 0x4B; // High byte of ODA AID (0x4BD7) + message->mel_len = 0xD7; // Low byte of ODA AID (0x4BD7) + + // Pack the BlockB, BlockC, and BlockD data from the RDS group into the packet + message->mel_data[0] = (rdsgroup.BlockB >> 8) & 0xFF; + message->mel_data[1] = rdsgroup.BlockB & 0xFF; + message->mel_data[2] = (rdsgroup.BlockC >> 8) & 0xFF; + message->mel_data[3] = rdsgroup.BlockC & 0xFF; + message->mel_data[4] = (rdsgroup.BlockD >> 8) & 0xFF; + message->mel_data[5] = rdsgroup.BlockD & 0xFF; + + frame.seq = UECP_DF_SEQ_DISABLED; + frame.msg_len = 4 + 6; // mec, dsn, psn, mel_data + 6 bytes + + // Convert the UECP data frame into a packet and queue it up + m_uecp_packets.emplace(uecp_create_data_packet(frame)); + } +} + +//--------------------------------------------------------------------------- +// rdsdecoder::decode_radiotext +// +// Decodes Group Type 2A and 2B - RadioText +// +// Arguments: +// +// rdsgroup - RDS group to be processed + +void rdsdecoder::decode_radiotext(tRDS_GROUPS const& rdsgroup) +{ + bool hascr = false; // Flag if carriage return detected + + // Get the text segment address and A/B indicators from the data + uint8_t textsegmentaddress = rdsgroup.BlockB & 0x000F; + uint8_t const ab = (rdsgroup.BlockB >> 4) & 0x01; + + // Set the initial A/B flag the first time it's been seen + if (!m_rt_init) + { + m_rt_ab = ab; + m_rt_init = true; + } + + // Clear any existing radio text when the A/B flag changes + if (ab != m_rt_ab) + { + + m_rt_ab = ab; // Toggle A/B + m_rt_data.fill(0x00); // Clear existing RT data + m_rt_ready = 0x0000; // Clear existing segment flags + } + + // Determine if this is Group A or Group B data + bool const groupa = ((rdsgroup.BlockB & 0x0800) == 0x0000); + + // Group A contains two RadioText segments in block C and D + if (groupa) + { + + size_t offset = static_cast(textsegmentaddress << 2); + m_rt_data[offset + 0] = (rdsgroup.BlockC >> 8) & 0xFF; + m_rt_data[offset + 1] = rdsgroup.BlockC & 0xFF; + m_rt_data[offset + 2] = (rdsgroup.BlockD >> 8) & 0xFF; + m_rt_data[offset + 3] = rdsgroup.BlockD & 0xFF; + + // Check if a carriage return has been sent in this text segment + hascr = ((m_rt_data[offset + 0] == 0x0D) || (m_rt_data[offset + 1] == 0x0D) || + (m_rt_data[offset + 2] == 0x0D) || (m_rt_data[offset + 3] == 0x0D)); + } + + // Group B contains one RadioText segment in block D + else + { + + size_t offset = static_cast(textsegmentaddress << 1); + m_rt_data[offset + 0] = (rdsgroup.BlockD >> 8) & 0xFF; + m_rt_data[offset + 1] = rdsgroup.BlockD & 0xFF; + + // Check if a carriage return has been sent in this text segment + hascr = ((m_rt_data[offset + 0] == 0x0D) || (m_rt_data[offset + 1] == 0x0D)); + } + + // Indicate that this segment has been received, and if a CR was detected flag + // all remaining text segments as received (they're not going to come anyway) + m_rt_ready |= (0x01 << textsegmentaddress); + while ((hascr) && (++textsegmentaddress < 16)) + { + + // Clear any RT information that may have been previously set + size_t offset = static_cast(textsegmentaddress << 2); + m_rt_data[offset + 0] = 0x00; + m_rt_data[offset + 1] = 0x00; + if (groupa) + m_rt_data[offset + 2] = 0x00; + if (groupa) + m_rt_data[offset + 3] = 0x00; + + // Flag this segment as ready in the bitmask + m_rt_ready |= (0x01 << textsegmentaddress); + } + + // The RT information is ready to be sent if all 16 segments have been retrieved + // for a Group A signal, or the first 8 segments of a Group B signal + if ((groupa) ? m_rt_ready == 0xFFFF : ((m_rt_ready & 0x00FF) == 0x00FF)) + { + + // UECP_MEC_RT + // + struct uecp_data_frame frame = {}; + struct uecp_message* message = &frame.msg; + + message->mec = UECP_MEC_RT; + message->dsn = UECP_MSG_DSN_CURRENT_SET; + message->psn = UECP_MSG_PSN_MAIN; + + message->mel_len = 0x01; + message->mel_data[0] = m_rt_ab; + + for (size_t index = 0; index < m_rt_data.size(); index++) + { + + if (m_rt_data[index] == 0x00) + break; + message->mel_data[message->mel_len++] = m_rt_data[index]; + } + + frame.seq = UECP_DF_SEQ_DISABLED; + frame.msg_len = 4 + message->mel_len; + + // Convert the UECP data frame into a packet and queue it up + m_uecp_packets.emplace(uecp_create_data_packet(frame)); + + // Reset the segment accumulator back to zero + m_rt_ready = 0x0000; + } +} + +//--------------------------------------------------------------------------- +// rdsdecoder::decode_rbds_programidentification +// +// Decodes RBDS Program Identification (PI) +// +// Arguments: +// +// rdsgroup - RDS group to be processed + +void rdsdecoder::decode_rbds_programidentification(tRDS_GROUPS const& rdsgroup) +{ + uint16_t pi = rdsgroup.BlockA; + + // Indicate a change to the Program Identification flags + if (pi != m_rbds_pi) + { + + uint8_t countrycode = 0xA0; // US + + m_rbds_pi = pi; + m_rbds_callsign.fill('\0'); + m_rbds_nationalcode.clear(); + + // SPECIAL CASE: AFxx -> xx00 + // + if ((pi & 0xAF00) == 0xAF00) + pi <<= 8; + + // SPECIAL CASE: Axxx -> x0xx + // + if ((pi & 0xA000) == 0xA000) + pi = (((pi & 0xF00) << 4) | (pi & 0xFF)); + + // NATIONALLY/REGIONALLY-LINKED RADIO STATION CODES + // + if ((pi & 0xB000) == 0xB000) + { + + // The low byte of these PI codes defines what nationally/regionally linked + // station code should be returned instead of a station call sign + switch (pi & 0xFF) + { + + case 0x0001: + m_rbds_nationalcode = "NPR-1"; + break; + case 0x0002: + m_rbds_nationalcode = "CBC Radio One"; + break; + case 0x0003: + m_rbds_nationalcode = "CBC Radio Two"; + break; + case 0x0004: + m_rbds_nationalcode = "CBC Premiere Chaine"; + break; + case 0x0005: + m_rbds_nationalcode = "CBC Espace Musique"; + break; + case 0x0006: + m_rbds_nationalcode = "CBC"; + break; + case 0x0007: + m_rbds_nationalcode = "CBC"; + break; + case 0x0008: + m_rbds_nationalcode = "CBC"; + break; + case 0x0009: + m_rbds_nationalcode = "CBC"; + break; + case 0x000A: + m_rbds_nationalcode = "NPR-2"; + break; + case 0x000B: + m_rbds_nationalcode = "NPR-3"; + break; + case 0x000C: + m_rbds_nationalcode = "NPR-4"; + break; + case 0x000D: + m_rbds_nationalcode = "NPR-5"; + break; + case 0x000E: + m_rbds_nationalcode = "NPR-6"; + break; + } + + // If a Canadian Nationally/Regionally linked code was set switch to Canada + if (!m_rbds_nationalcode.empty() && (m_rbds_nationalcode[0] == 'C')) + countrycode = 0xA1; + } + + // USA 3-LETTER-ONLY (ref: NRSC-4-B 04.2011 Table D.7) + // + else if ((pi >= 0x9950) && (pi <= 0x9EFF)) + { + + // The 3-letter only callsigns are static and represented in a lookup table + for (auto const iterator : CALL3TABLE) + { + + if (iterator.pi == pi) + { + + m_rbds_callsign[0] = iterator.csign[0]; + m_rbds_callsign[1] = iterator.csign[1]; + m_rbds_callsign[2] = iterator.csign[2]; + break; + } + } + } + + // USA EAST (Wxxx) + // + else if ((pi >= 21672) && (pi <= 39247)) + { + + uint16_t char1 = (pi - 21672) / 676; + uint16_t char2 = ((pi - 21672) - (char1 * 676)) / 26; + uint16_t char3 = ((pi - 21672) - (char1 * 676) - (char2 * 26)); + + m_rbds_callsign[0] = 'W'; + m_rbds_callsign[1] = static_cast(static_cast('A') + char1); + m_rbds_callsign[2] = static_cast(static_cast('A') + char2); + m_rbds_callsign[3] = static_cast(static_cast('A') + char3); + } + + // USA WEST (Kxxx) + // + else if ((pi >= 4096) && (pi <= 21671)) + { + + uint16_t char1 = (pi - 4096) / 676; + uint16_t char2 = ((pi - 4096) - (char1 * 676)) / 26; + uint16_t char3 = ((pi - 4096) - (char1 * 676) - (char2 * 26)); + + m_rbds_callsign[0] = 'K'; + m_rbds_callsign[1] = static_cast(static_cast('A') + char1); + m_rbds_callsign[2] = static_cast(static_cast('A') + char2); + m_rbds_callsign[3] = static_cast(static_cast('A') + char3); + } + + // CANADA + // + // Reverse engineered from "Program information codes for radio broadcasting stations" + // (https://www.ic.gc.ca/eic/site/smt-gst.nsf/eng/h_sf08741.html) + // + else if ((pi & 0xC000) == 0xC000) + { + + countrycode = 0xA1; // CA + + // Determine the offset and increment values from the PI code + uint16_t offset = ((pi - 0xC000) - 257) / 255; + uint16_t increment = (pi - 0xC000) - offset; + + // Calculate the individual character code values (interpretation differs for each) + uint16_t char1 = (increment - 257) / (26 * 27); + uint16_t char2 = ((increment - 257) - (char1 * (26 * 27))) / 27; + uint16_t char3 = ((increment - 257) - (char1 * (26 * 27))) - (char2 * 27); + + // Convert the second character of the call sign first as there is a small range of + // documented valid characters available; anything out of range should be ignored + if (char1 == 0) + m_rbds_callsign[1] = 'F'; + else if (char1 == 1) + m_rbds_callsign[1] = 'H'; + else if (char1 == 2) + m_rbds_callsign[1] = 'I'; + else if (char1 == 3) + m_rbds_callsign[1] = 'J'; + else if (char1 == 4) + m_rbds_callsign[1] = 'K'; + + // Only fill in the rest of the callsign if char1 was in the valid range + if (m_rbds_callsign[1] != '\0') + { + + // The first character is always 'C' + m_rbds_callsign[0] = 'C'; + + // The third character is always present and is zero-based from 'A' + m_rbds_callsign[2] = static_cast(static_cast('A') + char2); + + // The fourth character is optional and one-based from 'A' + if (char3 != 0) + m_rbds_callsign[3] = static_cast(static_cast('A') + (char3 - 1)); + } + } + + // MEXICO + // + else if ((pi & 0xF000) == 0xF000) + { + + countrycode = 0xA5; // MX + + // TODO - I need some manner of reference material here + } + + // Generate a couple fake UECP packets anytime the PI changes to allow Kodi + // to get the internals right for North American broadcasts with RBDS + struct uecp_data_frame frame = {}; + struct uecp_message* message = &frame.msg; + + // UECP_MEC_PI + // + message->mec = UECP_MEC_PI; + message->dsn = UECP_MSG_DSN_CURRENT_SET; + message->psn = UECP_MSG_PSN_MAIN; + + // Kodi expects a single word for PI at the address of mel_len; for RBDS + // hard-code it to 0xB000 which points to this row in the lookup data which + // has all three required country codes "US", "CA" and "MX" and can be properly + // set via the UECP_EPP_TM_INFO packet by specifying a PSN of 0xA0 for "US", + // 0xA1 for "CA", and 0xA5 for "MX": + // + // {"US","CA","BR","DO","LC","MX","__"}, // B + // + *reinterpret_cast(&message->mel_len) = 0xB0; + *reinterpret_cast(&message->mel_data[0]) = 0x00; + + frame.seq = UECP_DF_SEQ_DISABLED; + frame.msg_len = 3 + 2; // mec, dsn, psn + mel_data[2] + + // Convert the UECP data frame into a packet and queue it up + m_uecp_packets.emplace(uecp_create_data_packet(frame)); + + // UECP_EPP_TM_INFO + // + message->mec = UECP_EPP_TM_INFO; + message->dsn = UECP_MSG_DSN_CURRENT_SET; + message->psn = countrycode; + + frame.seq = UECP_DF_SEQ_DISABLED; + frame.msg_len = 3; // mec, dsn, psn + + // Convert the UECP data frame into a packet and queue it up + m_uecp_packets.emplace(uecp_create_data_packet(frame)); + } +} + +//--------------------------------------------------------------------------- +// rdsdecoder::decode_rdsgroup +// +// Decodes the next RDS group +// +// Arguments: +// +// rdsgroup - Next RDS group to be processed + +void rdsdecoder::decode_rdsgroup(tRDS_GROUPS const& rdsgroup) +{ + // Ignore spurious RDS packets that contain no data + // todo: figure out how this happens; could be a bug in the FM DSP + if ((rdsgroup.BlockA == 0x0000) && (rdsgroup.BlockB == 0x0000) && (rdsgroup.BlockC == 0x0000) && + (rdsgroup.BlockD == 0x0000)) + return; + + // Determine the group type code + uint8_t grouptypecode = (rdsgroup.BlockB >> 12) & 0x0F; + + // Program Identification + // + if (m_isrbds) + decode_rbds_programidentification(rdsgroup); + else + decode_programidentification(rdsgroup); + + // Program Type + // + decode_programtype(rdsgroup); + + // Group Type 0: Basic Tuning and switching information + if (grouptypecode == 0) + decode_basictuning(rdsgroup); + + // Group Type 1: Slow Labelling Codes + else if (grouptypecode == 1) + decode_slowlabellingcodes(rdsgroup); + + // Group Type 2: RadioText + else if (grouptypecode == 2) + decode_radiotext(rdsgroup); + + // Group Type 3: Application Identification + else if (grouptypecode == 3) + decode_applicationidentification(rdsgroup); + + // RadioText+ (RT+) Open Data Application + if (m_oda_rtplus && grouptypecode == m_rtplus_group) + decode_radiotextplus(rdsgroup); +} + +//--------------------------------------------------------------------------- +// rdsdecoder::decode_slowlabellingcodes +// +// Decodes Group 1A - Slow Labelling Codes +// +// Arguments: +// +// rdsgroup - RDS group to be processed + +void rdsdecoder::decode_slowlabellingcodes(tRDS_GROUPS const& rdsgroup) +{ + // Determine if this is Group A or Group B data + bool const groupa = ((rdsgroup.BlockB & 0x0800) == 0x0000); + + if (groupa) + { + + // UECP_MEC_SLOW_LABEL_CODES + // + struct uecp_data_frame frame = {}; + struct uecp_message* message = &frame.msg; + + message->mec = UECP_MEC_SLOW_LABEL_CODES; + message->dsn = UECP_MSG_DSN_CURRENT_SET; + //message->psn = UECP_MSG_PSN_MAIN; + + // For whatever reason, Kodi expects the high byte of the data to + // be in the message PSN field -- could be a bug in Kodi, but whatever + message->psn = ((rdsgroup.BlockC >> 8) & 0xFF); + *reinterpret_cast(&message->mel_len) = (rdsgroup.BlockC & 0xFF); + + frame.seq = UECP_DF_SEQ_DISABLED; + frame.msg_len = 3 + 1; // mec, dsn, psn + mel_data[1] + + // Convert the UECP data frame into a packet and queue it up + m_uecp_packets.emplace(uecp_create_data_packet(frame)); + } +} + +//--------------------------------------------------------------------------- +// rdsdecoder::get_rdbs_callsign +// +// Retrieves the RBDS call sign (if present) +// +// Arguments: +// +// NONE + +std::string rdsdecoder::get_rbds_callsign(void) const +{ + // If this is a nationally/regionally linked station return that string + if (!m_rbds_nationalcode.empty()) + return m_rbds_nationalcode; + + // The callsign may not have every letter assigned, trim any NULLs at the end + std::string callsign = std::string(m_rbds_callsign.begin(), m_rbds_callsign.end()); + auto trimpos = callsign.find('\0'); + if (trimpos != std::string::npos) + callsign.erase(trimpos); + + // If this wasn't a nationally/regionally-linked station, append the "-FM" suffix + return callsign + "-FM"; +} + +//--------------------------------------------------------------------------- +// rdsdecoder::has_radiotextplus +// +// Flag indicating that the RadioText+ (RT+) ODA is present +// +// Arguments: +// +// NONE + +bool rdsdecoder::has_radiotextplus(void) const +{ + return m_oda_rtplus; +} + +//--------------------------------------------------------------------------- +// rdsdecoder::has_rbds_callsign +// +// Flag indicating that the RDBS call sign has been decoded +// +// Arguments: +// +// NONE + +bool rdsdecoder::has_rbds_callsign(void) const +{ + // SPECIAL CASE: If the first nibble of the RBDS PI code is 1 the callsign data + // cannot be decoded if the RDS-TMC ODA is also present (NRSC-4-B, section D.4.7) + if (((m_rbds_pi & 0x1000) == 0x1000) && (m_oda_rdstmc)) + return false; + + return (!m_rbds_nationalcode.empty()) || (m_rbds_callsign[0] != '\0'); +} + +//--------------------------------------------------------------------------- +// rdsdecoder::has_rdstmc +// +// Flag indicating that the Traffic Message Channel (RDS-TMC) ODA is present +// +// Arguments: +// +// NONE + +bool rdsdecoder::has_rdstmc(void) const +{ + return m_oda_rdstmc; +} + +//--------------------------------------------------------------------------- +// rdsdecoder::pop_uecp_data_packet +// +// Pops the topmost UECP data packet out of the packet queue +// +// Arguments: +// +// packet - On success, contains the UECP packet data + +bool rdsdecoder::pop_uecp_data_packet(uecp_data_packet& packet) +{ + if (m_uecp_packets.empty()) + return false; + + // Swap the front packet from the top of the queue and pop it off + m_uecp_packets.front().swap(packet); + m_uecp_packets.pop(); + + return true; +} + +//--------------------------------------------------------------------------- + +#pragma warning(pop) diff --git a/src/rdsdecoder.h b/src/rdsdecoder.h index 191f4e0..f1f88a0 100644 --- a/src/rdsdecoder.h +++ b/src/rdsdecoder.h @@ -1,188 +1,188 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//----------------------------------------------------------------------------- - -#ifndef __RDSDECODER_H_ -#define __RDSDECODER_H_ -#pragma once - -#include "fmdsp/demodulator.h" -#include "uecp.h" - -#include -#include -#include - -#pragma warning(push, 4) - -//--------------------------------------------------------------------------- -// Class rdsdecoder -// -// Implements an RDS decoder to convert the data from the demodulator into -// UECP packets that will be understood by Kodi - -class rdsdecoder -{ -public: - // Instance Constructor - // - rdsdecoder(bool isrbds); - - // Destructor - // - ~rdsdecoder(); - - //----------------------------------------------------------------------- - // Member Functions - - // decode_rdsgroup - // - // Decodes the next RDS group - void decode_rdsgroup(tRDS_GROUPS const& rdsgroup); - - // get_rdbs_callsign - // - // Retrieves the RBDS call sign if present - std::string get_rbds_callsign(void) const; - - // has_radiotextplus - // - // Flag indicating that the RadioText+ (RT+) ODA is present - bool has_radiotextplus(void) const; - - // has_rbds_callsign - // - // Flag indicating that the RDBS call sign has been decoded - bool has_rbds_callsign(void) const; - - // has_rdstmc - // - // Flag indicating that the Traffic Message Channel (RDS-TMC) ODA is present - bool has_rdstmc(void) const; - - // pop_uecp_data_packet - // - // Pops the topmost UECP data packet from the queue - bool pop_uecp_data_packet(uecp_data_packet& frame); - -private: - rdsdecoder(rdsdecoder const&) = delete; - rdsdecoder& operator=(rdsdecoder const&) = delete; - - //----------------------------------------------------------------------- - // Private Type Declarations - - // uecp_packet_queue - // - // queue<> of formed UECP packets to be sent via the demultiplexer - using uecp_packet_queue = std::queue; - - //----------------------------------------------------------------------- - // Private Member Functions - - // decode_applicationidentification - // - // Decodes Group Type 3A - Application Identification - void decode_applicationidentification(tRDS_GROUPS const& rdsgroup); - - // decode_basictuning - // - // Decodes Group Type 0A and 0B - Basic Tuning and switching information - void decode_basictuning(tRDS_GROUPS const& rdsgroup); - - // decode_programidentification - // - // Decodes Program Identification (PI) - void decode_programidentification(tRDS_GROUPS const& rdsgroup); - - // decode_programtype - // - // Decodes Program Type (PTY) - void decode_programtype(tRDS_GROUPS const& rdsgroup); - - // decode_radiotext - // - // Decodes Group Type 2A and 2B - RadioText - void decode_radiotext(tRDS_GROUPS const& rdsgroup); - - // decode_radiotextplus - // - // Decodes the RadioText+ (RT+) ODA - void decode_radiotextplus(tRDS_GROUPS const& rdsgroup); - - // decode_rbds_programidentification - // - // Decodes RBDS Program Identification (PI) - void decode_rbds_programidentification(tRDS_GROUPS const& rdsgroup); - - // decode_slowlabellingcodes - // - // Decodes Group Type 1A - Slow Labelling Codes - void decode_slowlabellingcodes(tRDS_GROUPS const& rdsgroup); - - //----------------------------------------------------------------------- - // Member Variables - - const bool m_isrbds; // RBDS vs RDS flag - - // UECP - // - uecp_packet_queue m_uecp_packets; // Queued UECP packets - - // GENERAL - // - uint16_t m_pi = 0x0000; // PI indicator - uint8_t m_pty = 0x00; // PTY indicator - - // GROUP 0 - BASIC TUNING AND SWITCHING INFORMATION - // - uint8_t m_ps_ready = 0x00; // PS name ready indicator - std::array m_ps_data; // Program Service name - - // GROUP 2 - RADIOTEXT - // - bool m_rt_init = false; // RadioText init flag - uint16_t m_rt_ready = 0x0000; // RadioText ready indicator - uint8_t m_rt_ab = 0x00; // RadioText A/B flag - std::array m_rt_data; // RadioText data - - // GROUP 3 - OPEN DATA APPLICATION (ODA) FLAGS - // - bool m_oda_rtplus = false; // RadioText+ (RT+) - bool m_oda_rdstmc = false; // Traffic Message Channel (RDS-TMC) - - // RADIOTEXT+ - // - uint8_t m_rtplus_group = 0x00; // Group code for RadioText+ data - uint8_t m_rtplus_group_ab = 0x00; // A/B flag for RadioText+ group - - // RBDS - // - uint16_t m_rbds_pi = 0x0000; // RDBS PI indicator - std::string m_rbds_nationalcode; // RBDS Nationally Linked code - std::array m_rbds_callsign; // RDBS station call sign -}; - -//----------------------------------------------------------------------------- - -#pragma warning(pop) - -#endif // __RDSDECODER_H_ +//----------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef __RDSDECODER_H_ +#define __RDSDECODER_H_ +#pragma once + +#include "fmdsp/demodulator.h" +#include "uecp.h" + +#include +#include +#include + +#pragma warning(push, 4) + +//--------------------------------------------------------------------------- +// Class rdsdecoder +// +// Implements an RDS decoder to convert the data from the demodulator into +// UECP packets that will be understood by Kodi + +class rdsdecoder +{ +public: + // Instance Constructor + // + rdsdecoder(bool isrbds); + + // Destructor + // + ~rdsdecoder(); + + //----------------------------------------------------------------------- + // Member Functions + + // decode_rdsgroup + // + // Decodes the next RDS group + void decode_rdsgroup(tRDS_GROUPS const& rdsgroup); + + // get_rdbs_callsign + // + // Retrieves the RBDS call sign if present + std::string get_rbds_callsign(void) const; + + // has_radiotextplus + // + // Flag indicating that the RadioText+ (RT+) ODA is present + bool has_radiotextplus(void) const; + + // has_rbds_callsign + // + // Flag indicating that the RDBS call sign has been decoded + bool has_rbds_callsign(void) const; + + // has_rdstmc + // + // Flag indicating that the Traffic Message Channel (RDS-TMC) ODA is present + bool has_rdstmc(void) const; + + // pop_uecp_data_packet + // + // Pops the topmost UECP data packet from the queue + bool pop_uecp_data_packet(uecp_data_packet& frame); + +private: + rdsdecoder(rdsdecoder const&) = delete; + rdsdecoder& operator=(rdsdecoder const&) = delete; + + //----------------------------------------------------------------------- + // Private Type Declarations + + // uecp_packet_queue + // + // queue<> of formed UECP packets to be sent via the demultiplexer + using uecp_packet_queue = std::queue; + + //----------------------------------------------------------------------- + // Private Member Functions + + // decode_applicationidentification + // + // Decodes Group Type 3A - Application Identification + void decode_applicationidentification(tRDS_GROUPS const& rdsgroup); + + // decode_basictuning + // + // Decodes Group Type 0A and 0B - Basic Tuning and switching information + void decode_basictuning(tRDS_GROUPS const& rdsgroup); + + // decode_programidentification + // + // Decodes Program Identification (PI) + void decode_programidentification(tRDS_GROUPS const& rdsgroup); + + // decode_programtype + // + // Decodes Program Type (PTY) + void decode_programtype(tRDS_GROUPS const& rdsgroup); + + // decode_radiotext + // + // Decodes Group Type 2A and 2B - RadioText + void decode_radiotext(tRDS_GROUPS const& rdsgroup); + + // decode_radiotextplus + // + // Decodes the RadioText+ (RT+) ODA + void decode_radiotextplus(tRDS_GROUPS const& rdsgroup); + + // decode_rbds_programidentification + // + // Decodes RBDS Program Identification (PI) + void decode_rbds_programidentification(tRDS_GROUPS const& rdsgroup); + + // decode_slowlabellingcodes + // + // Decodes Group Type 1A - Slow Labelling Codes + void decode_slowlabellingcodes(tRDS_GROUPS const& rdsgroup); + + //----------------------------------------------------------------------- + // Member Variables + + const bool m_isrbds; // RBDS vs RDS flag + + // UECP + // + uecp_packet_queue m_uecp_packets; // Queued UECP packets + + // GENERAL + // + uint16_t m_pi = 0x0000; // PI indicator + uint8_t m_pty = 0x00; // PTY indicator + + // GROUP 0 - BASIC TUNING AND SWITCHING INFORMATION + // + uint8_t m_ps_ready = 0x00; // PS name ready indicator + std::array m_ps_data; // Program Service name + + // GROUP 2 - RADIOTEXT + // + bool m_rt_init = false; // RadioText init flag + uint16_t m_rt_ready = 0x0000; // RadioText ready indicator + uint8_t m_rt_ab = 0x00; // RadioText A/B flag + std::array m_rt_data; // RadioText data + + // GROUP 3 - OPEN DATA APPLICATION (ODA) FLAGS + // + bool m_oda_rtplus = false; // RadioText+ (RT+) + bool m_oda_rdstmc = false; // Traffic Message Channel (RDS-TMC) + + // RADIOTEXT+ + // + uint8_t m_rtplus_group = 0x00; // Group code for RadioText+ data + uint8_t m_rtplus_group_ab = 0x00; // A/B flag for RadioText+ group + + // RBDS + // + uint16_t m_rbds_pi = 0x0000; // RDBS PI indicator + std::string m_rbds_nationalcode; // RBDS Nationally Linked code + std::array m_rbds_callsign; // RDBS station call sign +}; + +//----------------------------------------------------------------------------- + +#pragma warning(pop) + +#endif // __RDSDECODER_H_ diff --git a/src/renderingcontrol.h b/src/renderingcontrol.h index a65264d..249ed58 100644 --- a/src/renderingcontrol.h +++ b/src/renderingcontrol.h @@ -1,188 +1,188 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//----------------------------------------------------------------------------- - -#ifndef __RENDERINGCONTROL_H_ -#define __RENDERINGCONTROL_H_ -#pragma once - -#include -#include -#include - -#pragma warning(push, 4) - -//----------------------------------------------------------------------------- -// Class renderingcontrol -// -// Replacement CRendering control; required since the one provided by Kodi -// cannot be made to work for either of its valid use cases. If you derive -// from CRendering, it will invoke a virtual member function during construction -// that needs to be overridden (oops). If you instead opt to use independent -// static callback functions, the required pre and post-rendering code will -// never get invoked, and you can't do that in your callbacks because the -// member variable you need is marked as private to CRendering. -// -// There is also another problem that can occur if the application is being -// shut down while a render control is active; it's calling Stop() after the -// object has been destroyed; just take Stop() out for now, I don't need it - -class ATTR_DLL_LOCAL renderingcontrol : public kodi::gui::CAddonGUIControlBase -{ -public: - // Instance Constructor - // - renderingcontrol(kodi::gui::CWindow* window, int controlid) : CAddonGUIControlBase(window) - { - // Find the control handle - m_controlHandle = m_interface->kodi_gui->window->get_control_render_addon( - m_interface->kodiBase, m_Window->GetControlHandle(), controlid); - - // Initialize the control callbacks; be advised this implicitly calls the OnCreate callback - if (m_controlHandle) - m_interface->kodi_gui->control_rendering->set_callbacks( - m_interface->kodiBase, m_controlHandle, this, OnCreate, OnRender, OnStop, OnDirty); - } - - // Destructor - // - ~renderingcontrol() override - { - // Destroy the control - m_interface->kodi_gui->control_rendering->destroy(m_interface->kodiBase, m_controlHandle); - } - - //---------------------------------------------------------------------------- - // Member Functions - //---------------------------------------------------------------------------- - - // dirty (virtual) - // - // Determines if a region is dirty and needs to be rendered - virtual bool dirty(void) { return false; } - - // render (virtual) - // - // Render all dirty regions - virtual void render(void) {} - - // Stop (virtual) - // - // Stop the rendering process - virtual void Stop(void) - { - } - -protected: - //------------------------------------------------------------------------- - // Protected Member Variables - //------------------------------------------------------------------------- - - size_t m_left{}; // Horizontal position of the control - size_t m_top{}; // Vertical position of the control - size_t m_width{}; // Width of the control - size_t m_height{}; // Height of the control - - kodi::HardwareContext m_device{}; // Device to use, only set for DirectX - -private: - // OnCreate - // - // Invoked by the underlying rendering control when it's being initialize - static bool OnCreate( - KODI_GUI_CLIENT_HANDLE handle, int x, int y, int w, int h, ADDON_HARDWARE_CONTEXT device) - { - assert(handle); - renderingcontrol* instance = reinterpret_cast(handle); - - // This is called during object construction, virtual member functions are not available. - // Store off all of the provided size, position, and device information - instance->m_left = static_cast(x); - instance->m_top = static_cast(y); - instance->m_width = static_cast(w); - instance->m_height = static_cast(h); - instance->m_device = device; - - // Access the rendering helper instance - instance->m_renderHelper = kodi::gui::GetRenderHelper(); - - return true; - } - - // OnDirty - // - // Invoked by the underlying rendering control to inquire about dirty regions - static bool OnDirty(KODI_GUI_CLIENT_HANDLE handle) - { - assert(handle); - return reinterpret_cast(handle)->dirty(); - } - - // OnRender - // - // Invoked by the underlying rendering control to render any dirty regions - static void OnRender(KODI_GUI_CLIENT_HANDLE handle) - { - assert(handle); - renderingcontrol* instance = reinterpret_cast(handle); - - if (!instance->m_renderHelper) - return; - - // Render the control - instance->m_renderHelper->Begin(); - instance->render(); - instance->m_renderHelper->End(); - } - - // OnStop - // - // Invoked by the underlying rendering control to stop the rendering process - static void OnStop(KODI_GUI_CLIENT_HANDLE /*handle*/) - { - // - // Removed implementation due to this being called after the object pointed - // to by handle has been destroyed if the application is closing. The render - // helper instance will be released automatically during destruction - // - - //assert(handle); - //renderingcontrol* instance = reinterpret_cast(handle); - - //// Stop rendering - //instance->Stop(); - - //// Reset the rendering helper instance pointer - //instance->m_renderHelper = nullptr; - } - - //------------------------------------------------------------------------- - // Member Variables - //------------------------------------------------------------------------- - - std::shared_ptr m_renderHelper; // Helper -}; - -//----------------------------------------------------------------------------- - -#pragma warning(pop) - -#endif // __RENDERINGCONTROL_H_ +//----------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef __RENDERINGCONTROL_H_ +#define __RENDERINGCONTROL_H_ +#pragma once + +#include +#include +#include + +#pragma warning(push, 4) + +//----------------------------------------------------------------------------- +// Class renderingcontrol +// +// Replacement CRendering control; required since the one provided by Kodi +// cannot be made to work for either of its valid use cases. If you derive +// from CRendering, it will invoke a virtual member function during construction +// that needs to be overridden (oops). If you instead opt to use independent +// static callback functions, the required pre and post-rendering code will +// never get invoked, and you can't do that in your callbacks because the +// member variable you need is marked as private to CRendering. +// +// There is also another problem that can occur if the application is being +// shut down while a render control is active; it's calling Stop() after the +// object has been destroyed; just take Stop() out for now, I don't need it + +class ATTR_DLL_LOCAL renderingcontrol : public kodi::gui::CAddonGUIControlBase +{ +public: + // Instance Constructor + // + renderingcontrol(kodi::gui::CWindow* window, int controlid) : CAddonGUIControlBase(window) + { + // Find the control handle + m_controlHandle = m_interface->kodi_gui->window->get_control_render_addon( + m_interface->kodiBase, m_Window->GetControlHandle(), controlid); + + // Initialize the control callbacks; be advised this implicitly calls the OnCreate callback + if (m_controlHandle) + m_interface->kodi_gui->control_rendering->set_callbacks( + m_interface->kodiBase, m_controlHandle, this, OnCreate, OnRender, OnStop, OnDirty); + } + + // Destructor + // + ~renderingcontrol() override + { + // Destroy the control + m_interface->kodi_gui->control_rendering->destroy(m_interface->kodiBase, m_controlHandle); + } + + //---------------------------------------------------------------------------- + // Member Functions + //---------------------------------------------------------------------------- + + // dirty (virtual) + // + // Determines if a region is dirty and needs to be rendered + virtual bool dirty(void) { return false; } + + // render (virtual) + // + // Render all dirty regions + virtual void render(void) {} + + // Stop (virtual) + // + // Stop the rendering process + virtual void Stop(void) + { + } + +protected: + //------------------------------------------------------------------------- + // Protected Member Variables + //------------------------------------------------------------------------- + + size_t m_left{}; // Horizontal position of the control + size_t m_top{}; // Vertical position of the control + size_t m_width{}; // Width of the control + size_t m_height{}; // Height of the control + + kodi::HardwareContext m_device{}; // Device to use, only set for DirectX + +private: + // OnCreate + // + // Invoked by the underlying rendering control when it's being initialize + static bool OnCreate( + KODI_GUI_CLIENT_HANDLE handle, int x, int y, int w, int h, ADDON_HARDWARE_CONTEXT device) + { + assert(handle); + renderingcontrol* instance = reinterpret_cast(handle); + + // This is called during object construction, virtual member functions are not available. + // Store off all of the provided size, position, and device information + instance->m_left = static_cast(x); + instance->m_top = static_cast(y); + instance->m_width = static_cast(w); + instance->m_height = static_cast(h); + instance->m_device = device; + + // Access the rendering helper instance + instance->m_renderHelper = kodi::gui::GetRenderHelper(); + + return true; + } + + // OnDirty + // + // Invoked by the underlying rendering control to inquire about dirty regions + static bool OnDirty(KODI_GUI_CLIENT_HANDLE handle) + { + assert(handle); + return reinterpret_cast(handle)->dirty(); + } + + // OnRender + // + // Invoked by the underlying rendering control to render any dirty regions + static void OnRender(KODI_GUI_CLIENT_HANDLE handle) + { + assert(handle); + renderingcontrol* instance = reinterpret_cast(handle); + + if (!instance->m_renderHelper) + return; + + // Render the control + instance->m_renderHelper->Begin(); + instance->render(); + instance->m_renderHelper->End(); + } + + // OnStop + // + // Invoked by the underlying rendering control to stop the rendering process + static void OnStop(KODI_GUI_CLIENT_HANDLE /*handle*/) + { + // + // Removed implementation due to this being called after the object pointed + // to by handle has been destroyed if the application is closing. The render + // helper instance will be released automatically during destruction + // + + //assert(handle); + //renderingcontrol* instance = reinterpret_cast(handle); + + //// Stop rendering + //instance->Stop(); + + //// Reset the rendering helper instance pointer + //instance->m_renderHelper = nullptr; + } + + //------------------------------------------------------------------------- + // Member Variables + //------------------------------------------------------------------------- + + std::shared_ptr m_renderHelper; // Helper +}; + +//----------------------------------------------------------------------------- + +#pragma warning(pop) + +#endif // __RENDERINGCONTROL_H_ diff --git a/src/scalar_condition.h b/src/scalar_condition.h index 4e1143b..c110bc4 100644 --- a/src/scalar_condition.h +++ b/src/scalar_condition.h @@ -1,112 +1,112 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//----------------------------------------------------------------------------- - -#ifndef __SCALAR_CONDITION_H_ -#define __SCALAR_CONDITION_H_ -#pragma once - -#include -#include -#include - -#pragma warning(push, 4) - -//----------------------------------------------------------------------------- -// scalar_condition -// -// Implements a simple scalar value condition variable - -template -class scalar_condition -{ -public: - // Instance Constructor - // - scalar_condition(_type initial) : m_value(initial) {} - - // Destructor - // - ~scalar_condition() = default; - - // assignment operator - // - const scalar_condition& operator=(_type const& value) - { - std::unique_lock critsec(m_lock); - - // Change the stored value and wake up any threads waiting for it, - // the predicate in WaitUntil handles spurious or no-change wakes - m_value = value; - m_condition.notify_all(); - - return *this; - } - - //------------------------------------------------------------------------- - // Member Functions - - // test - // - // Tests the condition by executing a zero-millisecond wait - bool test(_type const& value) const { return wait_until_equals(value, 0); } - - // wait_until_equals - // - // Waits indefinitely until the value has been set to the specified value - void wait_until_equals(_type const& value) const - { - std::unique_lock critsec(m_lock); - - // If the value does not already match the provided value, wait for it - m_condition.wait(critsec, [&]() -> bool { return m_value == value; }); - } - - // wait_until_equals - // - // Waits until the value has been set to the specified value - bool wait_until_equals(_type const& value, uint32_t timeoutms) const - { - std::unique_lock critsec(m_lock); - - // If the value does not already match the provided value, wait for it - return m_condition.wait_until( - critsec, std::chrono::system_clock::now() + std::chrono::milliseconds(timeoutms), - [&]() -> bool { return m_value == value; }); - } - -private: - scalar_condition(scalar_condition const&) = delete; - scalar_condition& operator=(scalar_condition const& rhs) = delete; - - //------------------------------------------------------------------------- - // Member Variables - - mutable std::condition_variable m_condition; // Condition variable - mutable std::mutex m_lock; // Synchronization object - _type m_value; // Contained value -}; - -//----------------------------------------------------------------------------- - -#pragma warning(pop) - -#endif // __SCALAR_CONDITION_H_ +//----------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef __SCALAR_CONDITION_H_ +#define __SCALAR_CONDITION_H_ +#pragma once + +#include +#include +#include + +#pragma warning(push, 4) + +//----------------------------------------------------------------------------- +// scalar_condition +// +// Implements a simple scalar value condition variable + +template +class scalar_condition +{ +public: + // Instance Constructor + // + scalar_condition(_type initial) : m_value(initial) {} + + // Destructor + // + ~scalar_condition() = default; + + // assignment operator + // + const scalar_condition& operator=(_type const& value) + { + std::unique_lock critsec(m_lock); + + // Change the stored value and wake up any threads waiting for it, + // the predicate in WaitUntil handles spurious or no-change wakes + m_value = value; + m_condition.notify_all(); + + return *this; + } + + //------------------------------------------------------------------------- + // Member Functions + + // test + // + // Tests the condition by executing a zero-millisecond wait + bool test(_type const& value) const { return wait_until_equals(value, 0); } + + // wait_until_equals + // + // Waits indefinitely until the value has been set to the specified value + void wait_until_equals(_type const& value) const + { + std::unique_lock critsec(m_lock); + + // If the value does not already match the provided value, wait for it + m_condition.wait(critsec, [&]() -> bool { return m_value == value; }); + } + + // wait_until_equals + // + // Waits until the value has been set to the specified value + bool wait_until_equals(_type const& value, uint32_t timeoutms) const + { + std::unique_lock critsec(m_lock); + + // If the value does not already match the provided value, wait for it + return m_condition.wait_until( + critsec, std::chrono::system_clock::now() + std::chrono::milliseconds(timeoutms), + [&]() -> bool { return m_value == value; }); + } + +private: + scalar_condition(scalar_condition const&) = delete; + scalar_condition& operator=(scalar_condition const& rhs) = delete; + + //------------------------------------------------------------------------- + // Member Variables + + mutable std::condition_variable m_condition; // Condition variable + mutable std::mutex m_lock; // Synchronization object + _type m_value; // Contained value +}; + +//----------------------------------------------------------------------------- + +#pragma warning(pop) + +#endif // __SCALAR_CONDITION_H_ diff --git a/src/signalmeter.cpp b/src/signalmeter.cpp index 3d85856..6204881 100644 --- a/src/signalmeter.cpp +++ b/src/signalmeter.cpp @@ -1,275 +1,275 @@ -//--------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//--------------------------------------------------------------------------- - -#include "signalmeter.h" - -#include "align.h" -#include "stdafx.h" -#include "string_exception.h" - -#include -#include -#include - -#pragma warning(push, 4) - -// signalmeter::DEFAULT_FFT_SIZE -// -// Default FFT size (bins) -size_t const signalmeter::DEFAULT_FFT_SIZE = 512; - -// signalmeter::RING_BUFFER_SIZE -// -// Input ring buffer size -size_t const signalmeter::RING_BUFFER_SIZE = (4 MiB); // 1s @ 2048000 - -//--------------------------------------------------------------------------- -// signalmeter Constructor (private) -// -// Arguments: -// -// signalprops - Signal properties -// plotprops - Signal plot properties -// rate - Signal status rate in milliseconds -// onstatus - Signal status callback function - -signalmeter::signalmeter(struct signalprops const& signalprops, - struct signalplotprops const& plotprops, - uint32_t rate, - status_callback const& onstatus) - : m_signalprops(signalprops), m_plotprops(plotprops), m_onstatus(onstatus) -{ - // Make sure the ring buffer is going to be big enough for the requested rate - size_t bufferrequired = - static_cast((signalprops.samplerate * 2) * (static_cast(rate) / 1000.0f)); - if (bufferrequired > RING_BUFFER_SIZE) - throw std::invalid_argument("rate"); - - // Allocate the input ring buffer - m_buffer = std::unique_ptr(new uint8_t[RING_BUFFER_SIZE]); - if (!m_buffer) - throw std::bad_alloc(); - - // The FFT size (bins) needs to be a power of two equal to or larger than the signal plot - // width; start at 512 and increase until we find that value - while (m_plotprops.width > m_fftsize) - m_fftsize <<= 1; - - // Calculate the number of bytes required in the input buffer to process and report - // updated signal statistics at (apporoximately) the requested rate in milliseconds - size_t bytespersecond = m_signalprops.samplerate * 2; - m_fftminbytes = - align::down(static_cast(bytespersecond * (static_cast(rate) / 1000.0f)), - static_cast(m_fftsize)); - - // Initialize the finite impulse response filter - m_fir.SetupParameters(static_cast(m_signalprops.lowcut), - static_cast(m_signalprops.highcut), - -static_cast(m_signalprops.offset), m_signalprops.samplerate); - - // Initialize the fast fourier transform instance - m_fft.SetFFTParams(static_cast(m_fftsize), false, 0.0, m_signalprops.samplerate); - m_fft.SetFFTAve(50); -} - -//--------------------------------------------------------------------------- -// signalmeter::create (static) -// -// Factory method, creates a new signalmeter instance -// -// Arguments: -// -// signalprops - Signal properties -// plotprops - Signal plot properties -// rate - Signal status rate in milliseconds -// onstatus - Signal status callback function - -std::unique_ptr signalmeter::create(struct signalprops const& signalprops, - struct signalplotprops const& plotprops, - uint32_t rate, - status_callback const& onstatus) -{ - return std::unique_ptr(new signalmeter(signalprops, plotprops, rate, onstatus)); -} - -//--------------------------------------------------------------------------- -// signalmeter::inputsamples -// -// Pipes input samples into the signal meter instance -// -// Arguments: -// -// samples - Pointer to the raw 8-bit I/Q samples from the device -// length - Length of the input data in bytes - -void signalmeter::inputsamples(uint8_t const* samples, size_t length) -{ - size_t byteswritten = 0; // Bytes written into ring buffer - - if ((samples == nullptr) || (length == 0)) - return; - - // Ensure there is enough space in the ring buffer to satisfy the operation - size_t available = (m_head < m_tail) ? m_tail - m_head : (RING_BUFFER_SIZE - m_head) + m_tail; - if (length > available) - throw string_exception("Insufficient ring buffer space to accomodate input"); - - // Write the input data into the ring buffer - size_t cb = length; - while (cb > 0) - { - - // If the head is behind the tail linearly, take the data between them otherwise - // take the data between the end of the buffer and the head - size_t chunk = - (m_head < m_tail) ? std::min(cb, m_tail - m_head) : std::min(cb, RING_BUFFER_SIZE - m_head); - memcpy(&m_buffer[m_head], &samples[byteswritten], chunk); - - m_head += chunk; // Increment the head position - byteswritten += chunk; // Increment number of bytes written - cb -= chunk; // Decrement remaining bytes - - // If the head has reached the end of the buffer, reset it back to zero - if (m_head >= RING_BUFFER_SIZE) - m_head = 0; - } - - assert(byteswritten == length); // Verify all bytes were written - processsamples(); // Process the available input samples -} - -//--------------------------------------------------------------------------- -// signalmeter::processsamples (private) -// -// Processes the available input samples -// -// Arguments: -// -// NONE - -void signalmeter::processsamples(void) -{ - // Allocate a heap buffer to store the converted I/Q samples for the FFT - std::unique_ptr samples(new TYPECPX[m_fftsize]); - if (!samples) - throw std::bad_alloc(); - - assert((m_fftminbytes % m_fftsize) == 0); - - // Determine how much data is available in the ring buffer for processing as I/Q samples - size_t available = (m_tail > m_head) ? (RING_BUFFER_SIZE - m_tail) + m_head : m_head - m_tail; - while (available >= m_fftminbytes) - { - - // Push all of the input samples into the FFT - for (size_t iterations = 0; iterations < (available / m_fftsize); iterations++) - { - - // Convert the raw 8-bit I/Q samples into scaled complex I/Q samples - for (size_t index = 0; index < m_fftsize; index++) - { - - // The FFT expects the I/Q samples in the range of -32767.0 through +32767.0 - // (32767.0 / 127.5) = 256.9960784313725 - samples[index] = { - -#ifdef FMDSP_USE_DOUBLE_PRECISION - (static_cast(m_buffer[m_tail]) - 127.5) * 256.9960784313725, // I - (static_cast(m_buffer[m_tail + 1]) - 127.5) * 256.9960784313725, // Q -#else - (static_cast(m_buffer[m_tail]) - 127.5f) * 256.9960784313725f, // I - (static_cast(m_buffer[m_tail + 1]) - 127.5f) * 256.9960784313725f, // Q -#endif - }; - - m_tail += 2; - if (m_tail >= RING_BUFFER_SIZE) - m_tail = 0; - } - - size_t numsamples = m_fftsize; - - // If specified, filter out everything but the desired bandwidth - if (m_signalprops.filter) - { - - numsamples = m_fir.ProcessData(static_cast(m_fftsize), &samples[0], &samples[0]); - assert(numsamples == m_fftsize); - } - - // Push the current set of samples through the fast fourier transform - m_fft.PutInDisplayFFT(static_cast(numsamples), &samples[0]); - - // Recalculate the amount of available data in the ring buffer after the read operation - available = (m_tail > m_head) ? (RING_BUFFER_SIZE - m_tail) + m_head : m_head - m_tail; - } - - // Convert the FFT into an integer-based signal plot - std::unique_ptr plot(new int[m_plotprops.width + 1]); - bool overload = m_fft.GetScreenIntegerFFTData( - static_cast(m_plotprops.height), static_cast(m_plotprops.width), - static_cast(m_plotprops.maxdb), static_cast(m_plotprops.mindb), - -(static_cast(m_signalprops.bandwidth) / 2) - m_signalprops.offset, - (static_cast(m_signalprops.bandwidth) / 2) - m_signalprops.offset, &plot[0]); - - // Determine how many Hertz are represented by each measurement in the signal plot and the center point - float hzper = - static_cast(m_plotprops.width) / static_cast(m_signalprops.bandwidth); - int32_t center = static_cast(m_plotprops.width) / 2; - - // Initialize a new signal_status structure with the proper signal type for the low/high cut indexes - struct signal_status status = {}; - - // Power is measured at the center frequency and smoothed - float power = ((m_plotprops.mindb - m_plotprops.maxdb) * - (static_cast(plot[center]) / static_cast(m_plotprops.height))) + - m_plotprops.maxdb; - status.power = m_avgpower = - (std::isnan(m_avgpower)) ? power : 0.85f * m_avgpower + 0.15f * power; - - // Noise is measured at the low and high cuts, averaged, and smoothed - status.lowcut = std::max(0, center + static_cast(m_signalprops.lowcut * hzper)); - status.highcut = std::min(static_cast(m_plotprops.width - 1), - center + static_cast(m_signalprops.highcut * hzper)); - float noise = ((m_plotprops.mindb - m_plotprops.maxdb) * - (static_cast((plot[status.lowcut] + plot[status.highcut]) / 2.0f) / - static_cast(m_plotprops.height))) + - m_plotprops.maxdb; - status.noise = m_avgnoise = - (std::isnan(m_avgnoise)) ? noise : 0.85f * m_avgnoise + 0.15f * noise; - - status.snr = m_avgpower + -m_avgnoise; // SNR in dB - status.overload = overload; // Overload flag - status.plotsize = m_plotprops.width; // Plot width - status.plotdata = &plot[0]; // Plot data - - // Invoke the callback to report the updated metrics and signal plot - m_onstatus(status); - - // Recalculate the amount of available data in the ring buffer - available = (m_tail > m_head) ? (RING_BUFFER_SIZE - m_tail) + m_head : m_head - m_tail; - } -} - -//--------------------------------------------------------------------------- - -#pragma warning(pop) +//--------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//--------------------------------------------------------------------------- + +#include "signalmeter.h" + +#include "align.h" +#include "stdafx.h" +#include "string_exception.h" + +#include +#include +#include + +#pragma warning(push, 4) + +// signalmeter::DEFAULT_FFT_SIZE +// +// Default FFT size (bins) +size_t const signalmeter::DEFAULT_FFT_SIZE = 512; + +// signalmeter::RING_BUFFER_SIZE +// +// Input ring buffer size +size_t const signalmeter::RING_BUFFER_SIZE = (4 MiB); // 1s @ 2048000 + +//--------------------------------------------------------------------------- +// signalmeter Constructor (private) +// +// Arguments: +// +// signalprops - Signal properties +// plotprops - Signal plot properties +// rate - Signal status rate in milliseconds +// onstatus - Signal status callback function + +signalmeter::signalmeter(struct signalprops const& signalprops, + struct signalplotprops const& plotprops, + uint32_t rate, + status_callback const& onstatus) + : m_signalprops(signalprops), m_plotprops(plotprops), m_onstatus(onstatus) +{ + // Make sure the ring buffer is going to be big enough for the requested rate + size_t bufferrequired = + static_cast((signalprops.samplerate * 2) * (static_cast(rate) / 1000.0f)); + if (bufferrequired > RING_BUFFER_SIZE) + throw std::invalid_argument("rate"); + + // Allocate the input ring buffer + m_buffer = std::unique_ptr(new uint8_t[RING_BUFFER_SIZE]); + if (!m_buffer) + throw std::bad_alloc(); + + // The FFT size (bins) needs to be a power of two equal to or larger than the signal plot + // width; start at 512 and increase until we find that value + while (m_plotprops.width > m_fftsize) + m_fftsize <<= 1; + + // Calculate the number of bytes required in the input buffer to process and report + // updated signal statistics at (apporoximately) the requested rate in milliseconds + size_t bytespersecond = m_signalprops.samplerate * 2; + m_fftminbytes = + align::down(static_cast(bytespersecond * (static_cast(rate) / 1000.0f)), + static_cast(m_fftsize)); + + // Initialize the finite impulse response filter + m_fir.SetupParameters(static_cast(m_signalprops.lowcut), + static_cast(m_signalprops.highcut), + -static_cast(m_signalprops.offset), m_signalprops.samplerate); + + // Initialize the fast fourier transform instance + m_fft.SetFFTParams(static_cast(m_fftsize), false, 0.0, m_signalprops.samplerate); + m_fft.SetFFTAve(50); +} + +//--------------------------------------------------------------------------- +// signalmeter::create (static) +// +// Factory method, creates a new signalmeter instance +// +// Arguments: +// +// signalprops - Signal properties +// plotprops - Signal plot properties +// rate - Signal status rate in milliseconds +// onstatus - Signal status callback function + +std::unique_ptr signalmeter::create(struct signalprops const& signalprops, + struct signalplotprops const& plotprops, + uint32_t rate, + status_callback const& onstatus) +{ + return std::unique_ptr(new signalmeter(signalprops, plotprops, rate, onstatus)); +} + +//--------------------------------------------------------------------------- +// signalmeter::inputsamples +// +// Pipes input samples into the signal meter instance +// +// Arguments: +// +// samples - Pointer to the raw 8-bit I/Q samples from the device +// length - Length of the input data in bytes + +void signalmeter::inputsamples(uint8_t const* samples, size_t length) +{ + size_t byteswritten = 0; // Bytes written into ring buffer + + if ((samples == nullptr) || (length == 0)) + return; + + // Ensure there is enough space in the ring buffer to satisfy the operation + size_t available = (m_head < m_tail) ? m_tail - m_head : (RING_BUFFER_SIZE - m_head) + m_tail; + if (length > available) + throw string_exception("Insufficient ring buffer space to accomodate input"); + + // Write the input data into the ring buffer + size_t cb = length; + while (cb > 0) + { + + // If the head is behind the tail linearly, take the data between them otherwise + // take the data between the end of the buffer and the head + size_t chunk = + (m_head < m_tail) ? std::min(cb, m_tail - m_head) : std::min(cb, RING_BUFFER_SIZE - m_head); + memcpy(&m_buffer[m_head], &samples[byteswritten], chunk); + + m_head += chunk; // Increment the head position + byteswritten += chunk; // Increment number of bytes written + cb -= chunk; // Decrement remaining bytes + + // If the head has reached the end of the buffer, reset it back to zero + if (m_head >= RING_BUFFER_SIZE) + m_head = 0; + } + + assert(byteswritten == length); // Verify all bytes were written + processsamples(); // Process the available input samples +} + +//--------------------------------------------------------------------------- +// signalmeter::processsamples (private) +// +// Processes the available input samples +// +// Arguments: +// +// NONE + +void signalmeter::processsamples(void) +{ + // Allocate a heap buffer to store the converted I/Q samples for the FFT + std::unique_ptr samples(new TYPECPX[m_fftsize]); + if (!samples) + throw std::bad_alloc(); + + assert((m_fftminbytes % m_fftsize) == 0); + + // Determine how much data is available in the ring buffer for processing as I/Q samples + size_t available = (m_tail > m_head) ? (RING_BUFFER_SIZE - m_tail) + m_head : m_head - m_tail; + while (available >= m_fftminbytes) + { + + // Push all of the input samples into the FFT + for (size_t iterations = 0; iterations < (available / m_fftsize); iterations++) + { + + // Convert the raw 8-bit I/Q samples into scaled complex I/Q samples + for (size_t index = 0; index < m_fftsize; index++) + { + + // The FFT expects the I/Q samples in the range of -32767.0 through +32767.0 + // (32767.0 / 127.5) = 256.9960784313725 + samples[index] = { + +#ifdef FMDSP_USE_DOUBLE_PRECISION + (static_cast(m_buffer[m_tail]) - 127.5) * 256.9960784313725, // I + (static_cast(m_buffer[m_tail + 1]) - 127.5) * 256.9960784313725, // Q +#else + (static_cast(m_buffer[m_tail]) - 127.5f) * 256.9960784313725f, // I + (static_cast(m_buffer[m_tail + 1]) - 127.5f) * 256.9960784313725f, // Q +#endif + }; + + m_tail += 2; + if (m_tail >= RING_BUFFER_SIZE) + m_tail = 0; + } + + size_t numsamples = m_fftsize; + + // If specified, filter out everything but the desired bandwidth + if (m_signalprops.filter) + { + + numsamples = m_fir.ProcessData(static_cast(m_fftsize), &samples[0], &samples[0]); + assert(numsamples == m_fftsize); + } + + // Push the current set of samples through the fast fourier transform + m_fft.PutInDisplayFFT(static_cast(numsamples), &samples[0]); + + // Recalculate the amount of available data in the ring buffer after the read operation + available = (m_tail > m_head) ? (RING_BUFFER_SIZE - m_tail) + m_head : m_head - m_tail; + } + + // Convert the FFT into an integer-based signal plot + std::unique_ptr plot(new int[m_plotprops.width + 1]); + bool overload = m_fft.GetScreenIntegerFFTData( + static_cast(m_plotprops.height), static_cast(m_plotprops.width), + static_cast(m_plotprops.maxdb), static_cast(m_plotprops.mindb), + -(static_cast(m_signalprops.bandwidth) / 2) - m_signalprops.offset, + (static_cast(m_signalprops.bandwidth) / 2) - m_signalprops.offset, &plot[0]); + + // Determine how many Hertz are represented by each measurement in the signal plot and the center point + float hzper = + static_cast(m_plotprops.width) / static_cast(m_signalprops.bandwidth); + int32_t center = static_cast(m_plotprops.width) / 2; + + // Initialize a new signal_status structure with the proper signal type for the low/high cut indexes + struct signal_status status = {}; + + // Power is measured at the center frequency and smoothed + float power = ((m_plotprops.mindb - m_plotprops.maxdb) * + (static_cast(plot[center]) / static_cast(m_plotprops.height))) + + m_plotprops.maxdb; + status.power = m_avgpower = + (std::isnan(m_avgpower)) ? power : 0.85f * m_avgpower + 0.15f * power; + + // Noise is measured at the low and high cuts, averaged, and smoothed + status.lowcut = std::max(0, center + static_cast(m_signalprops.lowcut * hzper)); + status.highcut = std::min(static_cast(m_plotprops.width - 1), + center + static_cast(m_signalprops.highcut * hzper)); + float noise = ((m_plotprops.mindb - m_plotprops.maxdb) * + (static_cast((plot[status.lowcut] + plot[status.highcut]) / 2.0f) / + static_cast(m_plotprops.height))) + + m_plotprops.maxdb; + status.noise = m_avgnoise = + (std::isnan(m_avgnoise)) ? noise : 0.85f * m_avgnoise + 0.15f * noise; + + status.snr = m_avgpower + -m_avgnoise; // SNR in dB + status.overload = overload; // Overload flag + status.plotsize = m_plotprops.width; // Plot width + status.plotdata = &plot[0]; // Plot data + + // Invoke the callback to report the updated metrics and signal plot + m_onstatus(status); + + // Recalculate the amount of available data in the ring buffer + available = (m_tail > m_head) ? (RING_BUFFER_SIZE - m_tail) + m_head : m_head - m_tail; + } +} + +//--------------------------------------------------------------------------- + +#pragma warning(pop) diff --git a/src/signalmeter.h b/src/signalmeter.h index fefd12a..fe98301 100644 --- a/src/signalmeter.h +++ b/src/signalmeter.h @@ -1,149 +1,149 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//----------------------------------------------------------------------------- - -#ifndef __SIGNALMETER_H_ -#define __SIGNALMETER_H_ -#pragma once - -#include "fmdsp/fastfir.h" -#include "fmdsp/fft.h" -#include "props.h" - -#include -#include - -#pragma warning(push, 4) - -//--------------------------------------------------------------------------- -// Class signalmeter -// -// Implements the FM signal meter - -class signalmeter -{ -public: - // Destructor - // - ~signalmeter() = default; - - //----------------------------------------------------------------------- - // Type Declarations - - // signal_status - // - // Structure used to report the current signal status - struct signal_status - { - - float power; // Signal power level in dB - float noise; // Signal noise level in dB - float snr; // Signal-to-noise ratio in dB - bool overload; // FFT input data is overloaded - int32_t lowcut; // Low cut plot index - int32_t highcut; // High cut plot index - size_t plotsize; // Size of the signal plot data array - int const* plotdata; // Pointer to the signal plot data - }; - - // exception_callback - // - // Callback function invoked when an exception has occurred on the worker thread - using exception_callback = std::function; - - // status_callback - // - // Callback function invoked when the signal status has changed - using status_callback = std::function; - - //----------------------------------------------------------------------- - // Member Functions - - // create (static) - // - // Factory method, creates a new signalmeter instance - static std::unique_ptr create(struct signalprops const& signalprops, - struct signalplotprops const& plotprops, - uint32_t rate, - status_callback const& onstatus); - - // inputsamples - // - // Pipes input samples into the signal meter instance - void inputsamples(uint8_t const* samples, size_t length); - -private: - signalmeter(signalmeter const&) = delete; - signalmeter& operator=(signalmeter const&) = delete; - - // DEFAULT_FFT_SIZE - // - // Default FFT size (bins) - static size_t const DEFAULT_FFT_SIZE; - - // RING_BUFFER_SIZE - // - // Input ring buffer size - static size_t const RING_BUFFER_SIZE; - - // Instance Constructor - // - signalmeter(struct signalprops const& signalprops, - struct signalplotprops const& plotprops, - uint32_t rate, - status_callback const& onstatus); - - //----------------------------------------------------------------------- - // Private Member Functions - - // processsamples - // - // Processes the available input samples - void processsamples(void); - - //----------------------------------------------------------------------- - // Member Variables - - struct signalprops const m_signalprops; // Signal properties - struct signalplotprops const m_plotprops; // Signal plot properties - status_callback const m_onstatus; // Status callback function - - // FFT - // - CFastFIR m_fir; // Finite impulse response filter - size_t m_fftsize{DEFAULT_FFT_SIZE}; // FFT size (bins) - CFft m_fft; // FFT instance - size_t m_fftminbytes{0}; // Number of bytes required to process - float m_avgpower{NAN}; // Average power level - float m_avgnoise{NAN}; // Average noise level - - // RING BUFFER - // - std::unique_ptr m_buffer; // Input ring buffer - size_t m_head{0}; // Ring buffer head position - size_t m_tail{0}; // Ring buffer tail position -}; - -//----------------------------------------------------------------------------- - -#pragma warning(pop) - -#endif // __SIGNALMETER_H_ +//----------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef __SIGNALMETER_H_ +#define __SIGNALMETER_H_ +#pragma once + +#include "fmdsp/fastfir.h" +#include "fmdsp/fft.h" +#include "props.h" + +#include +#include + +#pragma warning(push, 4) + +//--------------------------------------------------------------------------- +// Class signalmeter +// +// Implements the FM signal meter + +class signalmeter +{ +public: + // Destructor + // + ~signalmeter() = default; + + //----------------------------------------------------------------------- + // Type Declarations + + // signal_status + // + // Structure used to report the current signal status + struct signal_status + { + + float power; // Signal power level in dB + float noise; // Signal noise level in dB + float snr; // Signal-to-noise ratio in dB + bool overload; // FFT input data is overloaded + int32_t lowcut; // Low cut plot index + int32_t highcut; // High cut plot index + size_t plotsize; // Size of the signal plot data array + int const* plotdata; // Pointer to the signal plot data + }; + + // exception_callback + // + // Callback function invoked when an exception has occurred on the worker thread + using exception_callback = std::function; + + // status_callback + // + // Callback function invoked when the signal status has changed + using status_callback = std::function; + + //----------------------------------------------------------------------- + // Member Functions + + // create (static) + // + // Factory method, creates a new signalmeter instance + static std::unique_ptr create(struct signalprops const& signalprops, + struct signalplotprops const& plotprops, + uint32_t rate, + status_callback const& onstatus); + + // inputsamples + // + // Pipes input samples into the signal meter instance + void inputsamples(uint8_t const* samples, size_t length); + +private: + signalmeter(signalmeter const&) = delete; + signalmeter& operator=(signalmeter const&) = delete; + + // DEFAULT_FFT_SIZE + // + // Default FFT size (bins) + static size_t const DEFAULT_FFT_SIZE; + + // RING_BUFFER_SIZE + // + // Input ring buffer size + static size_t const RING_BUFFER_SIZE; + + // Instance Constructor + // + signalmeter(struct signalprops const& signalprops, + struct signalplotprops const& plotprops, + uint32_t rate, + status_callback const& onstatus); + + //----------------------------------------------------------------------- + // Private Member Functions + + // processsamples + // + // Processes the available input samples + void processsamples(void); + + //----------------------------------------------------------------------- + // Member Variables + + struct signalprops const m_signalprops; // Signal properties + struct signalplotprops const m_plotprops; // Signal plot properties + status_callback const m_onstatus; // Status callback function + + // FFT + // + CFastFIR m_fir; // Finite impulse response filter + size_t m_fftsize{DEFAULT_FFT_SIZE}; // FFT size (bins) + CFft m_fft; // FFT instance + size_t m_fftminbytes{0}; // Number of bytes required to process + float m_avgpower{NAN}; // Average power level + float m_avgnoise{NAN}; // Average noise level + + // RING BUFFER + // + std::unique_ptr m_buffer; // Input ring buffer + size_t m_head{0}; // Ring buffer head position + size_t m_tail{0}; // Ring buffer tail position +}; + +//----------------------------------------------------------------------------- + +#pragma warning(pop) + +#endif // __SIGNALMETER_H_ diff --git a/src/socket_exception.h b/src/socket_exception.h index 2d06019..f0f39b6 100644 --- a/src/socket_exception.h +++ b/src/socket_exception.h @@ -1,115 +1,115 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//----------------------------------------------------------------------------- - -#ifndef __SOCKET_EXCEPTION_H_ -#define __SOCKET_EXCEPTION_H_ -#pragma once - -#include -#include -#include - -#ifdef _WINDOWS -#include "win32_exception.h" -#endif - -#pragma warning(push, 4) - -//----------------------------------------------------------------------------- -// Class socket_exception -// -// std::exception used to describe a socket exception - -class socket_exception : public std::exception -{ -public: - // Instance Constructor - // - template - socket_exception(_args&&... args) - { - std::ostringstream stream; - format_message(stream, std::forward<_args>(args)...); - -#ifdef _WINDOWS - stream << ": " << win32_exception(static_cast(WSAGetLastError())); -#elif __clang__ - int code = errno; - char buf[512] = {}; - if (strerror_r(code, buf, std::extent::value) == 0) - stream << ": " << buf; - else - stream << ": socket_exception code " << code; -#else - int code = errno; - char buf[512] = {}; - stream << ": " << strerror_r(code, buf, std::extent::value); -#endif - - m_what = stream.str(); - } - - // Copy Constructor - // - socket_exception(socket_exception const& rhs) : m_what(rhs.m_what) {} - - // Move Constructor - // - socket_exception(socket_exception&& rhs) : m_what(std::move(rhs.m_what)) {} - - // char const* conversion operator - // - operator char const*() const { return m_what.c_str(); } - - //------------------------------------------------------------------------- - // Member Functions - - // what (std::exception) - // - // Gets a pointer to the exception message text - virtual char const* what(void) const noexcept override { return m_what.c_str(); } - -private: - //------------------------------------------------------------------------- - // Private Member Functions - - // format_message - // - // Variadic string generator used by the constructor - template - static void format_message(std::ostringstream& stream, _args&&... args) - { - int unpack[] = {0, (static_cast(stream << args), 0)...}; - (void)unpack; - } - - //------------------------------------------------------------------------- - // Member Variables - - std::string m_what; // Exception message -}; - -//----------------------------------------------------------------------------- - -#pragma warning(pop) - -#endif // __SOCKET_EXCEPTION_H_ +//----------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef __SOCKET_EXCEPTION_H_ +#define __SOCKET_EXCEPTION_H_ +#pragma once + +#include +#include +#include + +#ifdef _WINDOWS +#include "win32_exception.h" +#endif + +#pragma warning(push, 4) + +//----------------------------------------------------------------------------- +// Class socket_exception +// +// std::exception used to describe a socket exception + +class socket_exception : public std::exception +{ +public: + // Instance Constructor + // + template + socket_exception(_args&&... args) + { + std::ostringstream stream; + format_message(stream, std::forward<_args>(args)...); + +#ifdef _WINDOWS + stream << ": " << win32_exception(static_cast(WSAGetLastError())); +#elif __clang__ + int code = errno; + char buf[512] = {}; + if (strerror_r(code, buf, std::extent::value) == 0) + stream << ": " << buf; + else + stream << ": socket_exception code " << code; +#else + int code = errno; + char buf[512] = {}; + stream << ": " << strerror_r(code, buf, std::extent::value); +#endif + + m_what = stream.str(); + } + + // Copy Constructor + // + socket_exception(socket_exception const& rhs) : m_what(rhs.m_what) {} + + // Move Constructor + // + socket_exception(socket_exception&& rhs) : m_what(std::move(rhs.m_what)) {} + + // char const* conversion operator + // + operator char const*() const { return m_what.c_str(); } + + //------------------------------------------------------------------------- + // Member Functions + + // what (std::exception) + // + // Gets a pointer to the exception message text + virtual char const* what(void) const noexcept override { return m_what.c_str(); } + +private: + //------------------------------------------------------------------------- + // Private Member Functions + + // format_message + // + // Variadic string generator used by the constructor + template + static void format_message(std::ostringstream& stream, _args&&... args) + { + int unpack[] = {0, (static_cast(stream << args), 0)...}; + (void)unpack; + } + + //------------------------------------------------------------------------- + // Member Variables + + std::string m_what; // Exception message +}; + +//----------------------------------------------------------------------------- + +#pragma warning(pop) + +#endif // __SOCKET_EXCEPTION_H_ diff --git a/src/sqlite_exception.cpp b/src/sqlite_exception.cpp index 0fe20da..4a59f1c 100644 --- a/src/sqlite_exception.cpp +++ b/src/sqlite_exception.cpp @@ -1,96 +1,96 @@ -//--------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//--------------------------------------------------------------------------- - -#include "sqlite_exception.h" - -#include "stdafx.h" - -#pragma warning(push, 4) - -//----------------------------------------------------------------------------- -// sqlite_exception Constructor -// -// Arguments: -// -// code - SQLite error code - -sqlite_exception::sqlite_exception(int code) -{ - char* what = sqlite3_mprintf("%s (%d)", sqlite3_errstr(code), code); - m_what.assign((what) ? what : "sqlite_exception(code)"); - sqlite3_free(reinterpret_cast(what)); -} - -//----------------------------------------------------------------------------- -// sqlite_exception Constructor -// -// Arguments: -// -// code - SQLite error code -// message - Additional message to associate with the exception - -sqlite_exception::sqlite_exception(int code, char const* message) -{ - char* what = sqlite3_mprintf("%s (%d)", (message) ? message : sqlite3_errstr(code), code); - m_what.assign((what) ? what : "sqlite_exception(code, message)"); - sqlite3_free(reinterpret_cast(what)); -} - -//----------------------------------------------------------------------------- -// sqlite_exception Copy Constructor - -sqlite_exception::sqlite_exception(sqlite_exception const& rhs) : m_what(rhs.m_what) -{ -} - -//----------------------------------------------------------------------------- -// sqlite_exception Move Constructor - -sqlite_exception::sqlite_exception(sqlite_exception&& rhs) : m_what(std::move(rhs.m_what)) -{ -} - -//----------------------------------------------------------------------------- -// sqlite_exception char const* conversion operator - -sqlite_exception::operator char const*() const -{ - return m_what.c_str(); -} - -//----------------------------------------------------------------------------- -// sqlite_exception::what -// -// Gets a pointer to the exception message text -// -// Arguments: -// -// NONE - -char const* sqlite_exception::what(void) const noexcept -{ - return m_what.c_str(); -} - -//--------------------------------------------------------------------------- - -#pragma warning(pop) +//--------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//--------------------------------------------------------------------------- + +#include "sqlite_exception.h" + +#include "stdafx.h" + +#pragma warning(push, 4) + +//----------------------------------------------------------------------------- +// sqlite_exception Constructor +// +// Arguments: +// +// code - SQLite error code + +sqlite_exception::sqlite_exception(int code) +{ + char* what = sqlite3_mprintf("%s (%d)", sqlite3_errstr(code), code); + m_what.assign((what) ? what : "sqlite_exception(code)"); + sqlite3_free(reinterpret_cast(what)); +} + +//----------------------------------------------------------------------------- +// sqlite_exception Constructor +// +// Arguments: +// +// code - SQLite error code +// message - Additional message to associate with the exception + +sqlite_exception::sqlite_exception(int code, char const* message) +{ + char* what = sqlite3_mprintf("%s (%d)", (message) ? message : sqlite3_errstr(code), code); + m_what.assign((what) ? what : "sqlite_exception(code, message)"); + sqlite3_free(reinterpret_cast(what)); +} + +//----------------------------------------------------------------------------- +// sqlite_exception Copy Constructor + +sqlite_exception::sqlite_exception(sqlite_exception const& rhs) : m_what(rhs.m_what) +{ +} + +//----------------------------------------------------------------------------- +// sqlite_exception Move Constructor + +sqlite_exception::sqlite_exception(sqlite_exception&& rhs) : m_what(std::move(rhs.m_what)) +{ +} + +//----------------------------------------------------------------------------- +// sqlite_exception char const* conversion operator + +sqlite_exception::operator char const*() const +{ + return m_what.c_str(); +} + +//----------------------------------------------------------------------------- +// sqlite_exception::what +// +// Gets a pointer to the exception message text +// +// Arguments: +// +// NONE + +char const* sqlite_exception::what(void) const noexcept +{ + return m_what.c_str(); +} + +//--------------------------------------------------------------------------- + +#pragma warning(pop) diff --git a/src/stdafx.cpp b/src/stdafx.cpp index 8c467e6..a63185f 100644 --- a/src/stdafx.cpp +++ b/src/stdafx.cpp @@ -1,25 +1,25 @@ -//--------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//--------------------------------------------------------------------------- - -#include "stdafx.h" - -//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//--------------------------------------------------------------------------- + +#include "stdafx.h" + +//--------------------------------------------------------------------------- diff --git a/src/stdafx.h b/src/stdafx.h index afa9cd3..d5e1226 100644 --- a/src/stdafx.h +++ b/src/stdafx.h @@ -1,45 +1,45 @@ -//--------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//--------------------------------------------------------------------------- - -#ifndef __STDAFX_H_ -#define __STDAFX_H_ -#pragma once - -// KiB / MiB / GiB -// -#define KiB *(1 << 10) -#define MiB *(1 << 20) -#define GiB *(1 << 30) - -// KHz / MHz -// -#define KHz *(1000) -#define MHz *(1000000) - -// MS / US -// -#define MS *(1000) -#define US *(1000000) - -//--------------------------------------------------------------------------- - -#endif // __STDAFX_H_ +//--------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//--------------------------------------------------------------------------- + +#ifndef __STDAFX_H_ +#define __STDAFX_H_ +#pragma once + +// KiB / MiB / GiB +// +#define KiB *(1 << 10) +#define MiB *(1 << 20) +#define GiB *(1 << 30) + +// KHz / MHz +// +#define KHz *(1000) +#define MHz *(1000000) + +// MS / US +// +#define MS *(1000) +#define US *(1000000) + +//--------------------------------------------------------------------------- + +#endif // __STDAFX_H_ diff --git a/src/tcpdevice.h b/src/tcpdevice.h index 2955e86..3408e56 100644 --- a/src/tcpdevice.h +++ b/src/tcpdevice.h @@ -1,203 +1,203 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//----------------------------------------------------------------------------- - -#ifndef __TCPDEVICE_H_ -#define __TCPDEVICE_H_ -#pragma once - -#include "rtldevice.h" -#include "scalar_condition.h" - -#include -#include -#include -#include - -#pragma warning(push, 4) - -//--------------------------------------------------------------------------- -// Class tcpdevice -// -// Implements device management for an RTL-SDR connected over TCP. -// -// Portions based on: -// -// gr-osmosdr (rtl_tcp_source_c.cc) -// https://github.com/osmocom/gr-osmosdr -// Copyright (C) 2012 Dimitri Stolnikov -// GPLv3 - -class tcpdevice : public rtldevice -{ -public: - // Destructor - // - virtual ~tcpdevice(); - - //----------------------------------------------------------------------- - // Member Functions - - // begin_stream - // - // Starts streaming data from the device - void begin_stream(void) const override; - - // cancel_async - // - // Cancels any pending asynchronous read operations from the device - void cancel_async(void) const override; - - // create (static) - // - // Factory method, creates a new tcpdevice instance - static std::unique_ptr create(char const* host, uint16_t port); - - // get_device_name - // - // Gets the name of the device - char const* get_device_name(void) const override; - - // get_valid_gains - // - // Gets the valid tuner gain values for the device - void get_valid_gains(std::vector& dbs) const override; - - // read - // - // Reads data from the device - size_t read(uint8_t* buffer, size_t count) const override; - - // read_async - // - // Asynchronously reads data from the device - void read_async(rtldevice::asynccallback const& callback, uint32_t bufferlength) const override; - - // set_automatic_gain_control - // - // Enables/disables the automatic gain control of the device - void set_automatic_gain_control(bool enable) const override; - - // set_center_frequency - // - // Sets the center frequency of the device - uint32_t set_center_frequency(uint32_t hz) const override; - - // set_frequency_correction - // - // Sets the frequency correction of the device - int set_frequency_correction(int ppm) const override; - - // set_gain - // - // Sets the gain value of the device - int set_gain(int db) const override; - - // set_sample_rate - // - // Sets the sample rate of the device - uint32_t set_sample_rate(uint32_t hz) const override; - - // set_test_mode - // - // Enables/disables the test mode of the device - void set_test_mode(bool enable) const override; - -private: - tcpdevice(tcpdevice const&) = delete; - tcpdevice& operator=(tcpdevice const&) = delete; - - // Instance Constructor - // - tcpdevice(char const* host, uint16_t port); - - //----------------------------------------------------------------------- - // Private Type Declarations - - // struct command - // - // Structure used to send a command to the connected device -#ifdef _WINDOWS -#pragma pack(push, 1) - struct device_command - { - - uint8_t cmd; - uint32_t param; - }; -#pragma pack(pop) -#else - struct device_command - { - - uint8_t cmd; - uint32_t param; - } __attribute__((packed)); -#endif - - // struct device_info - // - // Retrieves information about the connected device (from rtl_tcp.c) - - struct device_info - { - - char magic[4]; - uint32_t tuner_type; - uint32_t tuner_gain_count; - }; - - // device_info structure size must be 12 bytes in length (rtl_tcp.c) - static_assert(sizeof(device_info) == 12, "device_info structure size must be 12 bytes in length"); - - //----------------------------------------------------------------------- - // Private Member Functions - - // close_socket (static) - // - // Closes an open socket, implementation specific - static void close_socket(int socket); - - //----------------------------------------------------------------------- - // Member Variables - - int m_socket = -1; // TCP/IP socket - rtlsdr_tuner m_tunertype = RTLSDR_TUNER_UNKNOWN; // Tuner type - std::string m_name; // Device name - - // ASYNCHRONOUS SUPPORT - // - mutable scalar_condition m_stop{false}; // Flag to stop async - mutable scalar_condition m_stopped{true}; // Async stopped condition - - static std::vector const s_gaintable_e4k; // RTLSDR_TUNER_E4000 - static std::vector const s_gaintable_fc0012; // RTLSDR_TUNER_FC0012 - static std::vector const s_gaintable_fc0013; // RTLSDR_TUNER_FC0013 - static std::vector const s_gaintable_fc2580; // RTLSDR_TUNER_FC2580 - static std::vector const s_gaintable_r82xx; // RTLSDR_TUNER_R820T/R828D - static std::vector const s_gaintable_unknown; // RTLSDR_TUNER_UNKNOWN -}; - -//----------------------------------------------------------------------------- - -#pragma warning(pop) - -#endif // __TCPDEVICE_H_ +//----------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef __TCPDEVICE_H_ +#define __TCPDEVICE_H_ +#pragma once + +#include "rtldevice.h" +#include "scalar_condition.h" + +#include +#include +#include +#include + +#pragma warning(push, 4) + +//--------------------------------------------------------------------------- +// Class tcpdevice +// +// Implements device management for an RTL-SDR connected over TCP. +// +// Portions based on: +// +// gr-osmosdr (rtl_tcp_source_c.cc) +// https://github.com/osmocom/gr-osmosdr +// Copyright (C) 2012 Dimitri Stolnikov +// GPLv3 + +class tcpdevice : public rtldevice +{ +public: + // Destructor + // + virtual ~tcpdevice(); + + //----------------------------------------------------------------------- + // Member Functions + + // begin_stream + // + // Starts streaming data from the device + void begin_stream(void) const override; + + // cancel_async + // + // Cancels any pending asynchronous read operations from the device + void cancel_async(void) const override; + + // create (static) + // + // Factory method, creates a new tcpdevice instance + static std::unique_ptr create(char const* host, uint16_t port); + + // get_device_name + // + // Gets the name of the device + char const* get_device_name(void) const override; + + // get_valid_gains + // + // Gets the valid tuner gain values for the device + void get_valid_gains(std::vector& dbs) const override; + + // read + // + // Reads data from the device + size_t read(uint8_t* buffer, size_t count) const override; + + // read_async + // + // Asynchronously reads data from the device + void read_async(rtldevice::asynccallback const& callback, uint32_t bufferlength) const override; + + // set_automatic_gain_control + // + // Enables/disables the automatic gain control of the device + void set_automatic_gain_control(bool enable) const override; + + // set_center_frequency + // + // Sets the center frequency of the device + uint32_t set_center_frequency(uint32_t hz) const override; + + // set_frequency_correction + // + // Sets the frequency correction of the device + int set_frequency_correction(int ppm) const override; + + // set_gain + // + // Sets the gain value of the device + int set_gain(int db) const override; + + // set_sample_rate + // + // Sets the sample rate of the device + uint32_t set_sample_rate(uint32_t hz) const override; + + // set_test_mode + // + // Enables/disables the test mode of the device + void set_test_mode(bool enable) const override; + +private: + tcpdevice(tcpdevice const&) = delete; + tcpdevice& operator=(tcpdevice const&) = delete; + + // Instance Constructor + // + tcpdevice(char const* host, uint16_t port); + + //----------------------------------------------------------------------- + // Private Type Declarations + + // struct command + // + // Structure used to send a command to the connected device +#ifdef _WINDOWS +#pragma pack(push, 1) + struct device_command + { + + uint8_t cmd; + uint32_t param; + }; +#pragma pack(pop) +#else + struct device_command + { + + uint8_t cmd; + uint32_t param; + } __attribute__((packed)); +#endif + + // struct device_info + // + // Retrieves information about the connected device (from rtl_tcp.c) + + struct device_info + { + + char magic[4]; + uint32_t tuner_type; + uint32_t tuner_gain_count; + }; + + // device_info structure size must be 12 bytes in length (rtl_tcp.c) + static_assert(sizeof(device_info) == 12, "device_info structure size must be 12 bytes in length"); + + //----------------------------------------------------------------------- + // Private Member Functions + + // close_socket (static) + // + // Closes an open socket, implementation specific + static void close_socket(int socket); + + //----------------------------------------------------------------------- + // Member Variables + + int m_socket = -1; // TCP/IP socket + rtlsdr_tuner m_tunertype = RTLSDR_TUNER_UNKNOWN; // Tuner type + std::string m_name; // Device name + + // ASYNCHRONOUS SUPPORT + // + mutable scalar_condition m_stop{false}; // Flag to stop async + mutable scalar_condition m_stopped{true}; // Async stopped condition + + static std::vector const s_gaintable_e4k; // RTLSDR_TUNER_E4000 + static std::vector const s_gaintable_fc0012; // RTLSDR_TUNER_FC0012 + static std::vector const s_gaintable_fc0013; // RTLSDR_TUNER_FC0013 + static std::vector const s_gaintable_fc2580; // RTLSDR_TUNER_FC2580 + static std::vector const s_gaintable_r82xx; // RTLSDR_TUNER_R820T/R828D + static std::vector const s_gaintable_unknown; // RTLSDR_TUNER_UNKNOWN +}; + +//----------------------------------------------------------------------------- + +#pragma warning(pop) + +#endif // __TCPDEVICE_H_ diff --git a/src/uecp.cpp b/src/uecp.cpp index fb053e1..0c1963a 100644 --- a/src/uecp.cpp +++ b/src/uecp.cpp @@ -1,123 +1,123 @@ -//--------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//--------------------------------------------------------------------------- - -#include "uecp.h" - -#include "stdafx.h" - -#include -#include - -#pragma warning(push, 4) - -//----------------------------------------------------------------------------- -// uecp_crc16_ccit -// -// Calculates the CRC of a UECP data frame -// -// Arguments: -// -// data - The buffer containing the data frame -// len - Length of the input buffer, in bytes -// -// rds-control (uecp.c) -// https://github.com/UoC-Radio/rds-control -// Copyright (C) 2013 Nick Kossifidis -// GPLv2 - -static uint16_t uecp_crc16_ccitt(uint8_t const* data, size_t len) -{ - uint16_t crc = 0xFFFF; - - for (size_t i = 0; i < len; i++) - { - crc = (uint8_t)(crc >> 8) | (crc << 8); - crc ^= data[i]; - crc ^= (uint8_t)(crc & 0xff) >> 4; - crc ^= (crc << 8) << 4; - crc ^= ((crc & 0xff) << 4) << 1; - } - - return ((crc ^= 0xFFFF) & 0xFFFF); -} - -//--------------------------------------------------------------------------- -// uecp_create_data_packet -// -// Creates a uecp_data_packet from a constructed uecp_data_frame -// -// Arguments: -// -// frame - uecp_data_frame to convert into a data packet - -uecp_data_packet uecp_create_data_packet(uecp_data_frame& frame) -{ - uecp_data_packet packet; // UECP data packet - - // Calculate the length of the data frame - size_t framelength = 4 + frame.msg_len; - - // Get a pointer to the raw frame data - uint8_t* framedata = reinterpret_cast(&frame); - - // Generate the CRC16 value for the frame and append it to the end - uint16_t framecrc = uecp_crc16_ccitt(reinterpret_cast(&frame), framelength); - framedata[framelength++] = (framecrc >> 8) & 0xFF; - framedata[framelength++] = (framecrc & 0xFF); - - packet.reserve(UECP_DP_MAX_LEN); // Reserve typical maximum - packet.push_back(UECP_DP_START_BYTE); // Start the packet - - // All of the data within the packet must be "byte stuffed", which indicates that - // any value greater than 0xFC requires a 2-byte escape sequence - for (size_t index = 0; index < framelength; index++) - { - - switch (framedata[index]) - { - - // 0xFD -> 0xFD+0x00 | 0xFE -> 0xFD+0x01 | 0xFF -> 0xFD+0x02 - case 0xFD: - packet.push_back(0xFD); - packet.push_back(0x00); - break; - case 0xFE: - packet.push_back(0xFD); - packet.push_back(0x01); - break; - case 0xFF: - packet.push_back(0xFD); - packet.push_back(0x02); - break; - default: - packet.push_back(framedata[index]); - } - } - - packet.push_back(UECP_DP_STOP_BYTE); // Finish the packet - - return packet; -} - -//--------------------------------------------------------------------------- - -#pragma warning(pop) +//--------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//--------------------------------------------------------------------------- + +#include "uecp.h" + +#include "stdafx.h" + +#include +#include + +#pragma warning(push, 4) + +//----------------------------------------------------------------------------- +// uecp_crc16_ccit +// +// Calculates the CRC of a UECP data frame +// +// Arguments: +// +// data - The buffer containing the data frame +// len - Length of the input buffer, in bytes +// +// rds-control (uecp.c) +// https://github.com/UoC-Radio/rds-control +// Copyright (C) 2013 Nick Kossifidis +// GPLv2 + +static uint16_t uecp_crc16_ccitt(uint8_t const* data, size_t len) +{ + uint16_t crc = 0xFFFF; + + for (size_t i = 0; i < len; i++) + { + crc = (uint8_t)(crc >> 8) | (crc << 8); + crc ^= data[i]; + crc ^= (uint8_t)(crc & 0xff) >> 4; + crc ^= (crc << 8) << 4; + crc ^= ((crc & 0xff) << 4) << 1; + } + + return ((crc ^= 0xFFFF) & 0xFFFF); +} + +//--------------------------------------------------------------------------- +// uecp_create_data_packet +// +// Creates a uecp_data_packet from a constructed uecp_data_frame +// +// Arguments: +// +// frame - uecp_data_frame to convert into a data packet + +uecp_data_packet uecp_create_data_packet(uecp_data_frame& frame) +{ + uecp_data_packet packet; // UECP data packet + + // Calculate the length of the data frame + size_t framelength = 4 + frame.msg_len; + + // Get a pointer to the raw frame data + uint8_t* framedata = reinterpret_cast(&frame); + + // Generate the CRC16 value for the frame and append it to the end + uint16_t framecrc = uecp_crc16_ccitt(reinterpret_cast(&frame), framelength); + framedata[framelength++] = (framecrc >> 8) & 0xFF; + framedata[framelength++] = (framecrc & 0xFF); + + packet.reserve(UECP_DP_MAX_LEN); // Reserve typical maximum + packet.push_back(UECP_DP_START_BYTE); // Start the packet + + // All of the data within the packet must be "byte stuffed", which indicates that + // any value greater than 0xFC requires a 2-byte escape sequence + for (size_t index = 0; index < framelength; index++) + { + + switch (framedata[index]) + { + + // 0xFD -> 0xFD+0x00 | 0xFE -> 0xFD+0x01 | 0xFF -> 0xFD+0x02 + case 0xFD: + packet.push_back(0xFD); + packet.push_back(0x00); + break; + case 0xFE: + packet.push_back(0xFD); + packet.push_back(0x01); + break; + case 0xFF: + packet.push_back(0xFD); + packet.push_back(0x02); + break; + default: + packet.push_back(framedata[index]); + } + } + + packet.push_back(UECP_DP_STOP_BYTE); // Finish the packet + + return packet; +} + +//--------------------------------------------------------------------------- + +#pragma warning(pop) diff --git a/src/uecp.h b/src/uecp.h index 92a4a90..1ed8312 100644 --- a/src/uecp.h +++ b/src/uecp.h @@ -1,161 +1,161 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//----------------------------------------------------------------------------- - -#ifndef __UECP_H_ -#define __UECP_H_ -#pragma once - -#include -#include - -#pragma warning(push, 4) - -//----------------------------------------------------------------------------- -// NOTE: The declarations in this header file were derived from the following -// source code references: -// -// rds-control (uecp.h) -// https://github.com/UoC-Radio/rds-control -// Copyright (C) 2013 Nick Kossifidis -// GPLv2 -// -// xbmc (xbmc/cores/VideoPlayer/VideoPlayerRadioRDS.cpp) -// https://github.com/xbmc/xbmc/ -// Copyright (C) 2005-2018 Team Kodi -// GPLv2 -//----------------------------------------------------------------------------- - -//--------------------------------------------------------------------------- -// UECP MESSAGE -//--------------------------------------------------------------------------- - -// Maximum UECP message size -// -#define UECP_MSG_LEN_MAX 255 -#define UECP_MSG_MEL_LEN_MAX UECP_MSG_LEN_MAX - 1 - -// uecp_message -// -// UECP message structure -#pragma pack(push, 1) -struct uecp_message -{ - - uint8_t mec; // Message element code - uint8_t dsn; // Data set number - uint8_t psn; // Program service number - uint8_t mel_len; // Message element length - uint8_t mel_data[UECP_MSG_MEL_LEN_MAX]; // Message element data -}; -#pragma pack(pop) - -// Message Element Codes -// -#define UECP_MEC_PI 0x01 // Program Identification -#define UECP_MEC_PS 0x02 // Program Service name -#define UECP_MEC_PIN 0x06 // Program Item Number -#define UECP_MEC_DI_PTYI 0x04 // Decoder Information / Dynamic PTY Indicator -#define UECP_MEC_TA_TP 0x03 // Traffic Anouncement / Traffic Programme -#define UECP_MEC_MS 0x05 // Music / Speech switch -#define UECP_MEC_PTY 0x07 // Program Type -#define UECP_MEC_PTYN 0x3A // Program Type Name -#define UECP_MEC_RT 0x0A // RadioText -#define UECP_MEC_AF 0x13 // Alternative Frequencies List -#define UECP_MEC_EON_AF 0x14 // Enhanced Other Networks Information -#define UECP_MEC_SLOW_LABEL_CODES 0x1A // Slow labeling codes -#define UECP_MEC_LINKAGE_INFO 0x2E // Linkage information -#define UECP_EPP_TM_INFO 0x31 // EPP transmitter information -#define UECP_ODA_DATA 0x46 // Open Data Application (ODA) data - -// Data Set Number -// -#define UECP_MSG_DSN_CURRENT_SET 0x00 -#define UECP_MSG_DSN_MIN 1 -#define UECP_MSG_DSN_MAX 0xFD -#define UECP_MSG_DSN_ALL_OTHER_SETS 0xFE -#define UECP_MSG_DSN_ALL_SETS 0xFF - -// Program Service Number -// -#define UECP_MSG_PSN_MAIN 0x00 -#define UECP_MSG_PSN_MIN 1 -#define UECP_MSG_PSN_MAX 0xFF - -// Message Element Length -// -#define UECP_MSG_MEL_NA 0xFF // mel_len is not applicable - -//----------------------------------------------------------------------------- -// UECP DATA FRAME -//----------------------------------------------------------------------------- - -// Maximum UECP data frame size -// -#define UECP_DF_MAX_LEN (UECP_MSG_LEN_MAX + 6) // <-- 261 - -// uecp_data_frame -// -// UECP data frame -#pragma pack(push, 1) -struct uecp_data_frame -{ - - uint16_t addr; // Remote address - uint8_t seq; // Sequence number - uint8_t msg_len; // Message Length - struct uecp_message msg; // Message (variable length) - uint16_t crc; // CRC (CCITT) -}; -#pragma pack(pop) - -// Sequence flags -// -#define UECP_DF_SEQ_DISABLED 0 - -//----------------------------------------------------------------------------- -// UECP DATA PACKET -//----------------------------------------------------------------------------- - -// uecp_data_packet -// -// Type definition for a UECP data packet -using uecp_data_packet = std::vector; - -// Maximum UECP data packet size -// -#define UECP_DP_MAX_LEN (UECP_DF_MAX_LEN + 2) // <-- 263 - -// Start/stop indicators -// -#define UECP_DP_START_BYTE 0xFE -#define UECP_DP_STOP_BYTE 0xFF - -// uecp_create_data_packet -// -// Creates a UECP data packet from the provided data frame -uecp_data_packet uecp_create_data_packet(uecp_data_frame& frame); - -//----------------------------------------------------------------------------- - -#pragma warning(pop) - -#endif // __UECP_H_ +//----------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef __UECP_H_ +#define __UECP_H_ +#pragma once + +#include +#include + +#pragma warning(push, 4) + +//----------------------------------------------------------------------------- +// NOTE: The declarations in this header file were derived from the following +// source code references: +// +// rds-control (uecp.h) +// https://github.com/UoC-Radio/rds-control +// Copyright (C) 2013 Nick Kossifidis +// GPLv2 +// +// xbmc (xbmc/cores/VideoPlayer/VideoPlayerRadioRDS.cpp) +// https://github.com/xbmc/xbmc/ +// Copyright (C) 2005-2018 Team Kodi +// GPLv2 +//----------------------------------------------------------------------------- + +//--------------------------------------------------------------------------- +// UECP MESSAGE +//--------------------------------------------------------------------------- + +// Maximum UECP message size +// +#define UECP_MSG_LEN_MAX 255 +#define UECP_MSG_MEL_LEN_MAX UECP_MSG_LEN_MAX - 1 + +// uecp_message +// +// UECP message structure +#pragma pack(push, 1) +struct uecp_message +{ + + uint8_t mec; // Message element code + uint8_t dsn; // Data set number + uint8_t psn; // Program service number + uint8_t mel_len; // Message element length + uint8_t mel_data[UECP_MSG_MEL_LEN_MAX]; // Message element data +}; +#pragma pack(pop) + +// Message Element Codes +// +#define UECP_MEC_PI 0x01 // Program Identification +#define UECP_MEC_PS 0x02 // Program Service name +#define UECP_MEC_PIN 0x06 // Program Item Number +#define UECP_MEC_DI_PTYI 0x04 // Decoder Information / Dynamic PTY Indicator +#define UECP_MEC_TA_TP 0x03 // Traffic Anouncement / Traffic Programme +#define UECP_MEC_MS 0x05 // Music / Speech switch +#define UECP_MEC_PTY 0x07 // Program Type +#define UECP_MEC_PTYN 0x3A // Program Type Name +#define UECP_MEC_RT 0x0A // RadioText +#define UECP_MEC_AF 0x13 // Alternative Frequencies List +#define UECP_MEC_EON_AF 0x14 // Enhanced Other Networks Information +#define UECP_MEC_SLOW_LABEL_CODES 0x1A // Slow labeling codes +#define UECP_MEC_LINKAGE_INFO 0x2E // Linkage information +#define UECP_EPP_TM_INFO 0x31 // EPP transmitter information +#define UECP_ODA_DATA 0x46 // Open Data Application (ODA) data + +// Data Set Number +// +#define UECP_MSG_DSN_CURRENT_SET 0x00 +#define UECP_MSG_DSN_MIN 1 +#define UECP_MSG_DSN_MAX 0xFD +#define UECP_MSG_DSN_ALL_OTHER_SETS 0xFE +#define UECP_MSG_DSN_ALL_SETS 0xFF + +// Program Service Number +// +#define UECP_MSG_PSN_MAIN 0x00 +#define UECP_MSG_PSN_MIN 1 +#define UECP_MSG_PSN_MAX 0xFF + +// Message Element Length +// +#define UECP_MSG_MEL_NA 0xFF // mel_len is not applicable + +//----------------------------------------------------------------------------- +// UECP DATA FRAME +//----------------------------------------------------------------------------- + +// Maximum UECP data frame size +// +#define UECP_DF_MAX_LEN (UECP_MSG_LEN_MAX + 6) // <-- 261 + +// uecp_data_frame +// +// UECP data frame +#pragma pack(push, 1) +struct uecp_data_frame +{ + + uint16_t addr; // Remote address + uint8_t seq; // Sequence number + uint8_t msg_len; // Message Length + struct uecp_message msg; // Message (variable length) + uint16_t crc; // CRC (CCITT) +}; +#pragma pack(pop) + +// Sequence flags +// +#define UECP_DF_SEQ_DISABLED 0 + +//----------------------------------------------------------------------------- +// UECP DATA PACKET +//----------------------------------------------------------------------------- + +// uecp_data_packet +// +// Type definition for a UECP data packet +using uecp_data_packet = std::vector; + +// Maximum UECP data packet size +// +#define UECP_DP_MAX_LEN (UECP_DF_MAX_LEN + 2) // <-- 263 + +// Start/stop indicators +// +#define UECP_DP_START_BYTE 0xFE +#define UECP_DP_STOP_BYTE 0xFF + +// uecp_create_data_packet +// +// Creates a UECP data packet from the provided data frame +uecp_data_packet uecp_create_data_packet(uecp_data_frame& frame); + +//----------------------------------------------------------------------------- + +#pragma warning(pop) + +#endif // __UECP_H_ diff --git a/src/usbdevice.cpp b/src/usbdevice.cpp index f4835b2..7699625 100644 --- a/src/usbdevice.cpp +++ b/src/usbdevice.cpp @@ -1,525 +1,525 @@ -//--------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//--------------------------------------------------------------------------- - -#include "usbdevice.h" - -#include "libusb_exception.h" -#include "stdafx.h" -#include "string_exception.h" - -#include -#include - -#ifdef __ANDROID__ -#include "kodi/platform/android/System.h" - -#include -#endif - -#pragma warning(push, 4) - -// usbdevice::DEFAULT_DEVICE_INDEX (static) -// -// Default device index value -uint32_t const usbdevice::DEFAULT_DEVICE_INDEX = 0; - -//--------------------------------------------------------------------------- -// usbdevice Constructor (private) -// -// Arguments: -// -// index - Device index - -usbdevice::usbdevice(uint32_t index) -{ - char manufacturer[256] = {'\0'}; // Manufacturer string - char product[256] = {'\0'}; // Product string - char serialnumber[256] = {'\0'}; // Serial number string - -#ifdef __ANDROID__ +//--------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//--------------------------------------------------------------------------- + +#include "usbdevice.h" + +#include "libusb_exception.h" +#include "stdafx.h" +#include "string_exception.h" + +#include +#include + +#ifdef __ANDROID__ +#include "kodi/platform/android/System.h" + +#include +#endif + +#pragma warning(push, 4) + +// usbdevice::DEFAULT_DEVICE_INDEX (static) +// +// Default device index value +uint32_t const usbdevice::DEFAULT_DEVICE_INDEX = 0; + +//--------------------------------------------------------------------------- +// usbdevice Constructor (private) +// +// Arguments: +// +// index - Device index + +usbdevice::usbdevice(uint32_t index) +{ + char manufacturer[256] = {'\0'}; // Manufacturer string + char product[256] = {'\0'}; // Product string + char serialnumber[256] = {'\0'}; // Serial number string + +#ifdef __ANDROID__ // NOTE: Before in original was LIBUSB_OPTION_ANDROID_JNIENV used, needs to investigate how. - kodi::platform::CInterfaceAndroidSystem system; - libusb_set_option(nullptr, LIBUSB_OPTION_NO_DEVICE_DISCOVERY, system.GetJNIEnv()); -#endif - - // Make sure that the specified index is going to correspond with an actual device - uint32_t devicecount = rtlsdr_get_device_count(); - if (index >= devicecount) - throw string_exception(__func__, ": invalid RTL-SDR device index"); - - // Attempt to open the RTL-SDR device with the specified index - int result = rtlsdr_open(&m_device, index); - if (result < 0) - throw string_exception(__func__, ": unable to open RTL-SDR device with index ", index); - - // Retrieve the name of the device with the specified index - char const* name = rtlsdr_get_device_name(index); - if (name != nullptr) - m_name.assign(name); - - try - { - - // The device type must not be UNKNOWN otherwise it won't work properly - enum rtlsdr_tuner type = rtlsdr_get_tuner_type(m_device); - if (type == rtlsdr_tuner::RTLSDR_TUNER_UNKNOWN) - throw string_exception(__func__, ": RTL-SDR device tuner type is unknown"); - - // Attempt to retrieve the USB strings for the connected device - result = rtlsdr_get_usb_strings(m_device, manufacturer, product, serialnumber); - if (result == 0) - { - - m_manufacturer.assign(manufacturer); - m_product.assign(product); - m_serialnumber.assign(serialnumber); - } - - // Turn off internal digital automatic gain control - result = rtlsdr_set_agc_mode(m_device, 0); - if (result < 0) - throw string_exception(__func__, ": failed to set digital automatic gain control to off"); - } - - // Close the RTL-SDR device on any thrown exception - catch (...) - { - rtlsdr_close(m_device); - m_device = nullptr; - throw; - } -} - -//--------------------------------------------------------------------------- -// usbdevice Destructor - -usbdevice::~usbdevice() -{ - if (m_device != nullptr) - rtlsdr_close(m_device); -} - -//--------------------------------------------------------------------------- -// usbdevice::begin_stream -// -// Starts streaming data from the device -// -// Arguments: -// -// NONE - -void usbdevice::begin_stream(void) const -{ - assert(m_device != nullptr); - - // Reset the device buffer to start the streaming interface - int result = rtlsdr_reset_buffer(m_device); - if (result < 0) - throw string_exception(__func__, ": unable to reset RTL-SDR device buffer"); -} - -//--------------------------------------------------------------------------- -// usbdevice::cancel_async -// -// Cancels any pending asynchronous read operations from the device -// -// Arguments: -// -// NONE - -void usbdevice::cancel_async(void) const -{ - assert(m_device != nullptr); - - rtlsdr_cancel_async(m_device); -} - -//--------------------------------------------------------------------------- -// usbdevice::create (static) -// -// Factory method, creates a new usbdevice instance -// -// Arguments: -// -// NONE - -std::unique_ptr usbdevice::create(void) -{ - return create(DEFAULT_DEVICE_INDEX); -} - -//--------------------------------------------------------------------------- -// usbdevice::create (static) -// -// Factory method, creates a new usbdevice instance -// -// Arguments: -// -// index - Device index - -std::unique_ptr usbdevice::create(uint32_t index) -{ - return std::unique_ptr(new usbdevice(index)); -} - -//--------------------------------------------------------------------------- -// usbdevice::get_center_frequency -// -// Gets the center frequency of the device -// -// Arguments: -// -// NONE - -uint32_t usbdevice::get_center_frequency(void) const -{ - assert(m_device != nullptr); - - return rtlsdr_get_center_freq(m_device); -} - -//--------------------------------------------------------------------------- -// usbdevice::get_device_name -// -// Gets the name of the device -// -// Arguments: -// -// NONE - -char const* usbdevice::get_device_name(void) const -{ - return m_name.c_str(); -} - -//--------------------------------------------------------------------------- -// usbdevice::get_frequency_correction -// -// Gets the frequency correction of the device -// -// Arguments: -// -// NONE - -int usbdevice::get_frequency_correction(void) const -{ - assert(m_device != nullptr); - - return rtlsdr_get_freq_correction(m_device); -} - -//--------------------------------------------------------------------------- -// usbdevice::get_gain -// -// Gets the gain of the device -// -// Arguments: -// -// NONE - -int usbdevice::get_gain(void) const -{ - assert(m_device != nullptr); - - return rtlsdr_get_tuner_gain(m_device); -} - -//--------------------------------------------------------------------------- -// usbdevice::get_manufacturer_name -// -// Gets the manufacturer name of the device -// -// Arguments: -// -// NONE - -char const* usbdevice::get_manufacturer_name(void) const -{ - return m_manufacturer.c_str(); -} - -//--------------------------------------------------------------------------- -// usbdevice::get_product_name -// -// Gets the product name of the device -// -// Arguments: -// -// NONE - -char const* usbdevice::get_product_name(void) const -{ - return m_product.c_str(); -} - -//--------------------------------------------------------------------------- -// usbdevice::get_sample_rate -// -// Gets the sample rate of the device -// -// Arguments: -// -// NONE - -uint32_t usbdevice::get_sample_rate(void) const -{ - assert(m_device != nullptr); - - return rtlsdr_get_sample_rate(m_device); -} - -//--------------------------------------------------------------------------- -// usbdevice::get_serial_number -// -// Gets the serial number of the device -// -// Arguments: -// -// NONE - -char const* usbdevice::get_serial_number(void) const -{ - return m_serialnumber.c_str(); -} - -//--------------------------------------------------------------------------- -// usbdevice::get_valid_gains -// -// Gets the valid tuner gain values for the device -// -// Arguments: -// -// dbs - vector<> to retrieve the valid gain values - -void usbdevice::get_valid_gains(std::vector& dbs) const -{ - assert(m_device != nullptr); - - dbs.clear(); - - // Determine the number of valid gain values for the tuner device - int numgains = rtlsdr_get_tuner_gains(m_device, nullptr); - if (numgains < 0) - throw string_exception(__func__, ": unable to determine valid tuner gain values"); - else if (numgains == 0) - return; - - // Reallocate the vector<> to hold all of the values and execute the operation again - dbs.resize(numgains); - if (rtlsdr_get_tuner_gains(m_device, dbs.data()) != numgains) - throw string_exception(__func__, ": size mismatch reading valid tuner gain values"); -} - -//--------------------------------------------------------------------------- -// usbdevice::read -// -// Reads data from the device -// -// Arguments: -// -// buffer - Buffer to receive the data -// count - Size of the destination buffer, specified in bytes - -size_t usbdevice::read(uint8_t* buffer, size_t count) const -{ - int bytesread = 0; // Bytes read from the device - - assert(m_device != nullptr); - - // rtlsdr_read_sync returns the underlying libusb error code when it fails - int result = rtlsdr_read_sync(m_device, buffer, static_cast(count), &bytesread); - if (result < 0) - throw string_exception(__func__, ": ", libusb_exception(result).what()); - - return static_cast(bytesread); -} - -//--------------------------------------------------------------------------- -// usbdevice::read_async -// -// Asynchronously reads data from the device -// -// Arguments: -// -// callback - Asynchronous read callback function -// bufferlength - Output buffer length in bytes - -void usbdevice::read_async(rtldevice::asynccallback const& callback, uint32_t bufferlength) const -{ - assert(m_device != nullptr); - - // rtlsdr_read_async_cb_t callback conversion function - auto callreadfunc = [](unsigned char* buf, uint32_t len, void* ctx) -> void - { - asynccallback const* func = reinterpret_cast(ctx); - if (func) - (*func)(reinterpret_cast(buf), static_cast(len)); - }; - - // Get the address of the callback std::function<> to pass as a context pointer - void const* pcallback = std::addressof(callback); - - // rtlsdr_read_async returns the underlying libusb error code when it fails - int result = - rtlsdr_read_async(m_device, callreadfunc, const_cast(pcallback), 0, bufferlength); - if (result < 0) - throw string_exception(__func__, ": ", libusb_exception(result).what()); -} - -//--------------------------------------------------------------------------- -// usbdevice::set_automatic_gain_control -// -// Enables/disables the automatic gain control mode of the device -// -// Arguments: -// -// enable - Flag to enable/disable test mode - -void usbdevice::set_automatic_gain_control(bool enable) const -{ - assert(m_device != nullptr); - - int result = rtlsdr_set_tuner_gain_mode(m_device, (enable) ? 0 : 1); - if (result < 0) - throw string_exception(__func__, ": failed to set tuner automatic gain control to ", - (enable) ? "on" : "off"); -} - -//--------------------------------------------------------------------------- -// usbdevice::set_center_frequency -// -// Sets the center frequency of the device -// -// Arguments: -// -// hz - Frequency to set, specified in hertz - -uint32_t usbdevice::set_center_frequency(uint32_t hz) const -{ - assert(m_device != nullptr); - - int result = rtlsdr_set_center_freq(m_device, hz); - if (result < 0) - throw string_exception(__func__, ": failed to set device frequency to ", hz, "Hz"); - - return rtlsdr_get_center_freq(m_device); -} - -//--------------------------------------------------------------------------- -// usbdevice::set_frequency_correction -// -// Sets the frequency correction of the device -// -// Arguments: -// -// ppm - Frequency correction to set, specified in parts per million - -int usbdevice::set_frequency_correction(int ppm) const -{ - assert(m_device != nullptr); - - // NOTE: rtlsdr_set_freq_correction will return -2 if the requested value matches what has - // already been applied to the device; this is not an error condition - int result = rtlsdr_set_freq_correction(m_device, ppm); - if ((result < 0) && (result != -2)) - throw string_exception(__func__, ": failed to set device frequency correction to ", ppm, "ppm"); - - return rtlsdr_get_freq_correction(m_device); -} - -//--------------------------------------------------------------------------- -// usbdevice::set_gain -// -// Sets the gain of the device -// -// Arguments: -// -// db - Gain to set, specified in tenths of a decibel - -int usbdevice::set_gain(int db) const -{ - std::vector validgains; // Gains allowed by the device - - assert(m_device != nullptr); - - // Get the list of valid gain values for the device - get_valid_gains(validgains); - if (validgains.size() == 0) - throw string_exception(__func__, ": failed to retrieve valid device gain values"); - - // Select the gain value that's closest to what has been requested - int nearest = validgains[0]; - for (size_t index = 0; index < validgains.size(); index++) - { - - if (std::abs(db - validgains[index]) < std::abs(db - nearest)) - nearest = validgains[index]; - } - - // Attempt to set the gain to the detected nearest gain value - int result = rtlsdr_set_tuner_gain(m_device, nearest); - if (result < 0) - throw string_exception(__func__, ": failed to set device gain to ", db, "dB/10"); - - // Return the gain value that was actually used - return nearest; -} - -//--------------------------------------------------------------------------- -// usbdevice::set_sample_rate -// -// Sets the sample rate of the device -// -// Arguments: -// -// hz - Sample rate to set, specified in hertz - -uint32_t usbdevice::set_sample_rate(uint32_t hz) const -{ - assert(m_device != nullptr); - - int result = rtlsdr_set_sample_rate(m_device, hz); - if (result < 0) - throw string_exception(__func__, ": failed to set device sample rate to ", hz, "Hz"); - - return rtlsdr_get_sample_rate(m_device); -} - -//--------------------------------------------------------------------------- -// usbdevice::set_test_mode -// -// Enables/disables the test mode of the device -// -// Arguments: -// -// enable - Flag to enable/disable test mode - -void usbdevice::set_test_mode(bool enable) const -{ - assert(m_device != nullptr); - - rtlsdr_set_testmode(m_device, (enable) ? 1 : 0); -} - -//--------------------------------------------------------------------------- - -#pragma warning(pop) + kodi::platform::CInterfaceAndroidSystem system; + libusb_set_option(nullptr, LIBUSB_OPTION_NO_DEVICE_DISCOVERY, system.GetJNIEnv()); +#endif + + // Make sure that the specified index is going to correspond with an actual device + uint32_t devicecount = rtlsdr_get_device_count(); + if (index >= devicecount) + throw string_exception(__func__, ": invalid RTL-SDR device index"); + + // Attempt to open the RTL-SDR device with the specified index + int result = rtlsdr_open(&m_device, index); + if (result < 0) + throw string_exception(__func__, ": unable to open RTL-SDR device with index ", index); + + // Retrieve the name of the device with the specified index + char const* name = rtlsdr_get_device_name(index); + if (name != nullptr) + m_name.assign(name); + + try + { + + // The device type must not be UNKNOWN otherwise it won't work properly + enum rtlsdr_tuner type = rtlsdr_get_tuner_type(m_device); + if (type == rtlsdr_tuner::RTLSDR_TUNER_UNKNOWN) + throw string_exception(__func__, ": RTL-SDR device tuner type is unknown"); + + // Attempt to retrieve the USB strings for the connected device + result = rtlsdr_get_usb_strings(m_device, manufacturer, product, serialnumber); + if (result == 0) + { + + m_manufacturer.assign(manufacturer); + m_product.assign(product); + m_serialnumber.assign(serialnumber); + } + + // Turn off internal digital automatic gain control + result = rtlsdr_set_agc_mode(m_device, 0); + if (result < 0) + throw string_exception(__func__, ": failed to set digital automatic gain control to off"); + } + + // Close the RTL-SDR device on any thrown exception + catch (...) + { + rtlsdr_close(m_device); + m_device = nullptr; + throw; + } +} + +//--------------------------------------------------------------------------- +// usbdevice Destructor + +usbdevice::~usbdevice() +{ + if (m_device != nullptr) + rtlsdr_close(m_device); +} + +//--------------------------------------------------------------------------- +// usbdevice::begin_stream +// +// Starts streaming data from the device +// +// Arguments: +// +// NONE + +void usbdevice::begin_stream(void) const +{ + assert(m_device != nullptr); + + // Reset the device buffer to start the streaming interface + int result = rtlsdr_reset_buffer(m_device); + if (result < 0) + throw string_exception(__func__, ": unable to reset RTL-SDR device buffer"); +} + +//--------------------------------------------------------------------------- +// usbdevice::cancel_async +// +// Cancels any pending asynchronous read operations from the device +// +// Arguments: +// +// NONE + +void usbdevice::cancel_async(void) const +{ + assert(m_device != nullptr); + + rtlsdr_cancel_async(m_device); +} + +//--------------------------------------------------------------------------- +// usbdevice::create (static) +// +// Factory method, creates a new usbdevice instance +// +// Arguments: +// +// NONE + +std::unique_ptr usbdevice::create(void) +{ + return create(DEFAULT_DEVICE_INDEX); +} + +//--------------------------------------------------------------------------- +// usbdevice::create (static) +// +// Factory method, creates a new usbdevice instance +// +// Arguments: +// +// index - Device index + +std::unique_ptr usbdevice::create(uint32_t index) +{ + return std::unique_ptr(new usbdevice(index)); +} + +//--------------------------------------------------------------------------- +// usbdevice::get_center_frequency +// +// Gets the center frequency of the device +// +// Arguments: +// +// NONE + +uint32_t usbdevice::get_center_frequency(void) const +{ + assert(m_device != nullptr); + + return rtlsdr_get_center_freq(m_device); +} + +//--------------------------------------------------------------------------- +// usbdevice::get_device_name +// +// Gets the name of the device +// +// Arguments: +// +// NONE + +char const* usbdevice::get_device_name(void) const +{ + return m_name.c_str(); +} + +//--------------------------------------------------------------------------- +// usbdevice::get_frequency_correction +// +// Gets the frequency correction of the device +// +// Arguments: +// +// NONE + +int usbdevice::get_frequency_correction(void) const +{ + assert(m_device != nullptr); + + return rtlsdr_get_freq_correction(m_device); +} + +//--------------------------------------------------------------------------- +// usbdevice::get_gain +// +// Gets the gain of the device +// +// Arguments: +// +// NONE + +int usbdevice::get_gain(void) const +{ + assert(m_device != nullptr); + + return rtlsdr_get_tuner_gain(m_device); +} + +//--------------------------------------------------------------------------- +// usbdevice::get_manufacturer_name +// +// Gets the manufacturer name of the device +// +// Arguments: +// +// NONE + +char const* usbdevice::get_manufacturer_name(void) const +{ + return m_manufacturer.c_str(); +} + +//--------------------------------------------------------------------------- +// usbdevice::get_product_name +// +// Gets the product name of the device +// +// Arguments: +// +// NONE + +char const* usbdevice::get_product_name(void) const +{ + return m_product.c_str(); +} + +//--------------------------------------------------------------------------- +// usbdevice::get_sample_rate +// +// Gets the sample rate of the device +// +// Arguments: +// +// NONE + +uint32_t usbdevice::get_sample_rate(void) const +{ + assert(m_device != nullptr); + + return rtlsdr_get_sample_rate(m_device); +} + +//--------------------------------------------------------------------------- +// usbdevice::get_serial_number +// +// Gets the serial number of the device +// +// Arguments: +// +// NONE + +char const* usbdevice::get_serial_number(void) const +{ + return m_serialnumber.c_str(); +} + +//--------------------------------------------------------------------------- +// usbdevice::get_valid_gains +// +// Gets the valid tuner gain values for the device +// +// Arguments: +// +// dbs - vector<> to retrieve the valid gain values + +void usbdevice::get_valid_gains(std::vector& dbs) const +{ + assert(m_device != nullptr); + + dbs.clear(); + + // Determine the number of valid gain values for the tuner device + int numgains = rtlsdr_get_tuner_gains(m_device, nullptr); + if (numgains < 0) + throw string_exception(__func__, ": unable to determine valid tuner gain values"); + else if (numgains == 0) + return; + + // Reallocate the vector<> to hold all of the values and execute the operation again + dbs.resize(numgains); + if (rtlsdr_get_tuner_gains(m_device, dbs.data()) != numgains) + throw string_exception(__func__, ": size mismatch reading valid tuner gain values"); +} + +//--------------------------------------------------------------------------- +// usbdevice::read +// +// Reads data from the device +// +// Arguments: +// +// buffer - Buffer to receive the data +// count - Size of the destination buffer, specified in bytes + +size_t usbdevice::read(uint8_t* buffer, size_t count) const +{ + int bytesread = 0; // Bytes read from the device + + assert(m_device != nullptr); + + // rtlsdr_read_sync returns the underlying libusb error code when it fails + int result = rtlsdr_read_sync(m_device, buffer, static_cast(count), &bytesread); + if (result < 0) + throw string_exception(__func__, ": ", libusb_exception(result).what()); + + return static_cast(bytesread); +} + +//--------------------------------------------------------------------------- +// usbdevice::read_async +// +// Asynchronously reads data from the device +// +// Arguments: +// +// callback - Asynchronous read callback function +// bufferlength - Output buffer length in bytes + +void usbdevice::read_async(rtldevice::asynccallback const& callback, uint32_t bufferlength) const +{ + assert(m_device != nullptr); + + // rtlsdr_read_async_cb_t callback conversion function + auto callreadfunc = [](unsigned char* buf, uint32_t len, void* ctx) -> void + { + asynccallback const* func = reinterpret_cast(ctx); + if (func) + (*func)(reinterpret_cast(buf), static_cast(len)); + }; + + // Get the address of the callback std::function<> to pass as a context pointer + void const* pcallback = std::addressof(callback); + + // rtlsdr_read_async returns the underlying libusb error code when it fails + int result = + rtlsdr_read_async(m_device, callreadfunc, const_cast(pcallback), 0, bufferlength); + if (result < 0) + throw string_exception(__func__, ": ", libusb_exception(result).what()); +} + +//--------------------------------------------------------------------------- +// usbdevice::set_automatic_gain_control +// +// Enables/disables the automatic gain control mode of the device +// +// Arguments: +// +// enable - Flag to enable/disable test mode + +void usbdevice::set_automatic_gain_control(bool enable) const +{ + assert(m_device != nullptr); + + int result = rtlsdr_set_tuner_gain_mode(m_device, (enable) ? 0 : 1); + if (result < 0) + throw string_exception(__func__, ": failed to set tuner automatic gain control to ", + (enable) ? "on" : "off"); +} + +//--------------------------------------------------------------------------- +// usbdevice::set_center_frequency +// +// Sets the center frequency of the device +// +// Arguments: +// +// hz - Frequency to set, specified in hertz + +uint32_t usbdevice::set_center_frequency(uint32_t hz) const +{ + assert(m_device != nullptr); + + int result = rtlsdr_set_center_freq(m_device, hz); + if (result < 0) + throw string_exception(__func__, ": failed to set device frequency to ", hz, "Hz"); + + return rtlsdr_get_center_freq(m_device); +} + +//--------------------------------------------------------------------------- +// usbdevice::set_frequency_correction +// +// Sets the frequency correction of the device +// +// Arguments: +// +// ppm - Frequency correction to set, specified in parts per million + +int usbdevice::set_frequency_correction(int ppm) const +{ + assert(m_device != nullptr); + + // NOTE: rtlsdr_set_freq_correction will return -2 if the requested value matches what has + // already been applied to the device; this is not an error condition + int result = rtlsdr_set_freq_correction(m_device, ppm); + if ((result < 0) && (result != -2)) + throw string_exception(__func__, ": failed to set device frequency correction to ", ppm, "ppm"); + + return rtlsdr_get_freq_correction(m_device); +} + +//--------------------------------------------------------------------------- +// usbdevice::set_gain +// +// Sets the gain of the device +// +// Arguments: +// +// db - Gain to set, specified in tenths of a decibel + +int usbdevice::set_gain(int db) const +{ + std::vector validgains; // Gains allowed by the device + + assert(m_device != nullptr); + + // Get the list of valid gain values for the device + get_valid_gains(validgains); + if (validgains.size() == 0) + throw string_exception(__func__, ": failed to retrieve valid device gain values"); + + // Select the gain value that's closest to what has been requested + int nearest = validgains[0]; + for (size_t index = 0; index < validgains.size(); index++) + { + + if (std::abs(db - validgains[index]) < std::abs(db - nearest)) + nearest = validgains[index]; + } + + // Attempt to set the gain to the detected nearest gain value + int result = rtlsdr_set_tuner_gain(m_device, nearest); + if (result < 0) + throw string_exception(__func__, ": failed to set device gain to ", db, "dB/10"); + + // Return the gain value that was actually used + return nearest; +} + +//--------------------------------------------------------------------------- +// usbdevice::set_sample_rate +// +// Sets the sample rate of the device +// +// Arguments: +// +// hz - Sample rate to set, specified in hertz + +uint32_t usbdevice::set_sample_rate(uint32_t hz) const +{ + assert(m_device != nullptr); + + int result = rtlsdr_set_sample_rate(m_device, hz); + if (result < 0) + throw string_exception(__func__, ": failed to set device sample rate to ", hz, "Hz"); + + return rtlsdr_get_sample_rate(m_device); +} + +//--------------------------------------------------------------------------- +// usbdevice::set_test_mode +// +// Enables/disables the test mode of the device +// +// Arguments: +// +// enable - Flag to enable/disable test mode + +void usbdevice::set_test_mode(bool enable) const +{ + assert(m_device != nullptr); + + rtlsdr_set_testmode(m_device, (enable) ? 1 : 0); +} + +//--------------------------------------------------------------------------- + +#pragma warning(pop) diff --git a/src/usbdevice.h b/src/usbdevice.h index 0a499b7..c664df2 100644 --- a/src/usbdevice.h +++ b/src/usbdevice.h @@ -1,181 +1,181 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//----------------------------------------------------------------------------- - -#ifndef __USBDEVICE_H_ -#define __USBDEVICE_H_ -#pragma once - -#include "rtldevice.h" - -#include -#include -#include -#include -#include - -#pragma warning(push, 4) - -//--------------------------------------------------------------------------- -// Class usbdevice -// -// Implements device management for a local USB connected RTL-SDR - -class usbdevice : public rtldevice -{ -public: - // DEFAULT_DEVICE_INDEX - // - // Default device index number - static uint32_t const DEFAULT_DEVICE_INDEX; - - // Destructor - // - virtual ~usbdevice(); - - //----------------------------------------------------------------------- - // Member Functions - - // begin_stream - // - // Starts streaming data from the device - void begin_stream(void) const override; - - // cancel_async - // - // Cancels any pending asynchronous read operations from the device - void cancel_async(void) const override; - - // create (static) - // - // Factory method, creates a new usbdevice instance - static std::unique_ptr create(void); - static std::unique_ptr create(uint32_t index); - - // get_center_frequency - // - // Gets the center frequency of the device - uint32_t get_center_frequency(void) const; - - // get_device_name - // - // Gets the name of the device - char const* get_device_name(void) const override; - - // get_frequency_correction - // - // Gets the frequency correction of the device - int get_frequency_correction(void) const; - - // get_gain - // - // Gets the gain value of the device - int get_gain(void) const; - - // get_manufacturer_name - // - // Gets the manufacturer name of the device - char const* get_manufacturer_name(void) const; - - // get_product_name - // - // Gets the product name of the device - char const* get_product_name(void) const; - - // get_sample_rate - // - // Gets the sample rate of the device - uint32_t get_sample_rate(void) const; - - // get_serial_number - // - // Gets the serial number of the device - char const* get_serial_number(void) const; - - // get_valid_gains - // - // Gets the valid tuner gain values for the device - void get_valid_gains(std::vector& dbs) const override; - - // read - // - // Reads data from the device - size_t read(uint8_t* buffer, size_t count) const override; - - // read_async - // - // Asynchronously reads data from the device - void read_async(rtldevice::asynccallback const& callback, uint32_t bufferlength) const override; - - // set_automatic_gain_control - // - // Enables/disables the automatic gain control of the device - void set_automatic_gain_control(bool enable) const override; - - // set_center_frequency - // - // Sets the center frequency of the device - uint32_t set_center_frequency(uint32_t hz) const override; - - // set_frequency_correction - // - // Sets the frequency correction of the device - int set_frequency_correction(int ppm) const override; - - // set_gain - // - // Sets the gain value of the device - int set_gain(int db) const override; - - // set_sample_rate - // - // Sets the sample rate of the device - uint32_t set_sample_rate(uint32_t hz) const override; - - // set_test_mode - // - // Enables/disables the test mode of the device - void set_test_mode(bool enable) const override; - -private: - usbdevice(usbdevice const&) = delete; - usbdevice& operator=(usbdevice const&) = delete; - - // Instance Constructor - // - usbdevice(uint32_t index); - - //----------------------------------------------------------------------- - // Member Variables - - rtlsdr_dev_t* m_device = nullptr; // Device instance - - std::string m_name; // Device name - std::string m_manufacturer; // Device manufacturer - std::string m_product; // Device product name - std::string m_serialnumber; // Device serial number -}; - -//----------------------------------------------------------------------------- - -#pragma warning(pop) - -#endif // __USBDEVICE_H_ +//----------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef __USBDEVICE_H_ +#define __USBDEVICE_H_ +#pragma once + +#include "rtldevice.h" + +#include +#include +#include +#include +#include + +#pragma warning(push, 4) + +//--------------------------------------------------------------------------- +// Class usbdevice +// +// Implements device management for a local USB connected RTL-SDR + +class usbdevice : public rtldevice +{ +public: + // DEFAULT_DEVICE_INDEX + // + // Default device index number + static uint32_t const DEFAULT_DEVICE_INDEX; + + // Destructor + // + virtual ~usbdevice(); + + //----------------------------------------------------------------------- + // Member Functions + + // begin_stream + // + // Starts streaming data from the device + void begin_stream(void) const override; + + // cancel_async + // + // Cancels any pending asynchronous read operations from the device + void cancel_async(void) const override; + + // create (static) + // + // Factory method, creates a new usbdevice instance + static std::unique_ptr create(void); + static std::unique_ptr create(uint32_t index); + + // get_center_frequency + // + // Gets the center frequency of the device + uint32_t get_center_frequency(void) const; + + // get_device_name + // + // Gets the name of the device + char const* get_device_name(void) const override; + + // get_frequency_correction + // + // Gets the frequency correction of the device + int get_frequency_correction(void) const; + + // get_gain + // + // Gets the gain value of the device + int get_gain(void) const; + + // get_manufacturer_name + // + // Gets the manufacturer name of the device + char const* get_manufacturer_name(void) const; + + // get_product_name + // + // Gets the product name of the device + char const* get_product_name(void) const; + + // get_sample_rate + // + // Gets the sample rate of the device + uint32_t get_sample_rate(void) const; + + // get_serial_number + // + // Gets the serial number of the device + char const* get_serial_number(void) const; + + // get_valid_gains + // + // Gets the valid tuner gain values for the device + void get_valid_gains(std::vector& dbs) const override; + + // read + // + // Reads data from the device + size_t read(uint8_t* buffer, size_t count) const override; + + // read_async + // + // Asynchronously reads data from the device + void read_async(rtldevice::asynccallback const& callback, uint32_t bufferlength) const override; + + // set_automatic_gain_control + // + // Enables/disables the automatic gain control of the device + void set_automatic_gain_control(bool enable) const override; + + // set_center_frequency + // + // Sets the center frequency of the device + uint32_t set_center_frequency(uint32_t hz) const override; + + // set_frequency_correction + // + // Sets the frequency correction of the device + int set_frequency_correction(int ppm) const override; + + // set_gain + // + // Sets the gain value of the device + int set_gain(int db) const override; + + // set_sample_rate + // + // Sets the sample rate of the device + uint32_t set_sample_rate(uint32_t hz) const override; + + // set_test_mode + // + // Enables/disables the test mode of the device + void set_test_mode(bool enable) const override; + +private: + usbdevice(usbdevice const&) = delete; + usbdevice& operator=(usbdevice const&) = delete; + + // Instance Constructor + // + usbdevice(uint32_t index); + + //----------------------------------------------------------------------- + // Member Variables + + rtlsdr_dev_t* m_device = nullptr; // Device instance + + std::string m_name; // Device name + std::string m_manufacturer; // Device manufacturer + std::string m_product; // Device product name + std::string m_serialnumber; // Device serial number +}; + +//----------------------------------------------------------------------------- + +#pragma warning(pop) + +#endif // __USBDEVICE_H_ diff --git a/src/win32_exception.cpp b/src/win32_exception.cpp index 11dfa59..06f5dad 100644 --- a/src/win32_exception.cpp +++ b/src/win32_exception.cpp @@ -1,141 +1,141 @@ -//--------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//--------------------------------------------------------------------------- - -#include "win32_exception.h" - -#pragma warning(push, 4) - -//----------------------------------------------------------------------------- -// win32_exception Constructor -// -// Arguments: -// -// code - Win32 error code - -win32_exception::win32_exception(DWORD code) -{ - char what[512] = {'\0'}; // Formatted exception string - - // For a normal DWORD error use decimal formatting - snprintf(what, std::extent::value, "%s (%u)", format_message(code).c_str(), code); - m_what.assign(what); -} - -//----------------------------------------------------------------------------- -// win32_exception Constructor -// -// Arguments: -// -// code - Win32 error code - -win32_exception::win32_exception(HRESULT code) -{ - char what[512] = {'\0'}; // Formatted exception string - - // For an HRESULT error use hexadecimal formatting - snprintf(what, std::extent::value, "%s (0x%08X)", format_message(code).c_str(), - code); - m_what.assign(what); -} - -//----------------------------------------------------------------------------- -// win32_exception Copy Constructor - -win32_exception::win32_exception(win32_exception const& rhs) : m_what(rhs.m_what) -{ -} - -//----------------------------------------------------------------------------- -// win32_exception Move Constructor - -win32_exception::win32_exception(win32_exception&& rhs) : m_what(std::move(rhs.m_what)) -{ -} - -//----------------------------------------------------------------------------- -// win32_exception char const* conversion operator - -win32_exception::operator char const*() const -{ - return m_what.c_str(); -} - -//----------------------------------------------------------------------------- -// win32_exception::what -// -// Gets a pointer to the exception message text -// -// Arguments: -// -// NONE - -char const* win32_exception::what(void) const noexcept -{ - return m_what.c_str(); -} - -//----------------------------------------------------------------------------- -// win32_exception::format_message (private, static) -// -// Generates the formatted message string -// -// Arguments: -// -// result - Windows system error code for which to generate the message - -std::string win32_exception::format_message(DWORD result) -{ - LPSTR formatted = nullptr; // Allocated string from ::FormatMessageA - std::string message; // std::string version of the formatted message - - // Attempt to format the message from the current module resources - DWORD cchformatted = FormatMessageA( - FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - nullptr, result, GetThreadUILanguage(), reinterpret_cast(&formatted), 0, nullptr); - if (cchformatted == 0) - { - - // The message could not be looked up in the specified module; generate the default message instead - if (formatted) - { - LocalFree(formatted); - formatted = nullptr; - } - cchformatted = FormatMessageA( - FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ARGUMENT_ARRAY, - "win32_exception code %1!lu!", 0, 0, reinterpret_cast(&formatted), 0, - reinterpret_cast(&result)); - } - - // If a formatted message was generated, assign it to the std::string and release it - if (cchformatted > 0) - message.assign(formatted, cchformatted); - if (formatted) - LocalFree(formatted); - - // Return the formatted message to the caller - return message; -} - -//----------------------------------------------------------------------------- - -#pragma warning(pop) +//--------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//--------------------------------------------------------------------------- + +#include "win32_exception.h" + +#pragma warning(push, 4) + +//----------------------------------------------------------------------------- +// win32_exception Constructor +// +// Arguments: +// +// code - Win32 error code + +win32_exception::win32_exception(DWORD code) +{ + char what[512] = {'\0'}; // Formatted exception string + + // For a normal DWORD error use decimal formatting + snprintf(what, std::extent::value, "%s (%u)", format_message(code).c_str(), code); + m_what.assign(what); +} + +//----------------------------------------------------------------------------- +// win32_exception Constructor +// +// Arguments: +// +// code - Win32 error code + +win32_exception::win32_exception(HRESULT code) +{ + char what[512] = {'\0'}; // Formatted exception string + + // For an HRESULT error use hexadecimal formatting + snprintf(what, std::extent::value, "%s (0x%08X)", format_message(code).c_str(), + code); + m_what.assign(what); +} + +//----------------------------------------------------------------------------- +// win32_exception Copy Constructor + +win32_exception::win32_exception(win32_exception const& rhs) : m_what(rhs.m_what) +{ +} + +//----------------------------------------------------------------------------- +// win32_exception Move Constructor + +win32_exception::win32_exception(win32_exception&& rhs) : m_what(std::move(rhs.m_what)) +{ +} + +//----------------------------------------------------------------------------- +// win32_exception char const* conversion operator + +win32_exception::operator char const*() const +{ + return m_what.c_str(); +} + +//----------------------------------------------------------------------------- +// win32_exception::what +// +// Gets a pointer to the exception message text +// +// Arguments: +// +// NONE + +char const* win32_exception::what(void) const noexcept +{ + return m_what.c_str(); +} + +//----------------------------------------------------------------------------- +// win32_exception::format_message (private, static) +// +// Generates the formatted message string +// +// Arguments: +// +// result - Windows system error code for which to generate the message + +std::string win32_exception::format_message(DWORD result) +{ + LPSTR formatted = nullptr; // Allocated string from ::FormatMessageA + std::string message; // std::string version of the formatted message + + // Attempt to format the message from the current module resources + DWORD cchformatted = FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, result, GetThreadUILanguage(), reinterpret_cast(&formatted), 0, nullptr); + if (cchformatted == 0) + { + + // The message could not be looked up in the specified module; generate the default message instead + if (formatted) + { + LocalFree(formatted); + formatted = nullptr; + } + cchformatted = FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ARGUMENT_ARRAY, + "win32_exception code %1!lu!", 0, 0, reinterpret_cast(&formatted), 0, + reinterpret_cast(&result)); + } + + // If a formatted message was generated, assign it to the std::string and release it + if (cchformatted > 0) + message.assign(formatted, cchformatted); + if (formatted) + LocalFree(formatted); + + // Return the formatted message to the caller + return message; +} + +//----------------------------------------------------------------------------- + +#pragma warning(pop) diff --git a/src/win32_exception.h b/src/win32_exception.h index 3540c34..d941d6c 100644 --- a/src/win32_exception.h +++ b/src/win32_exception.h @@ -1,85 +1,85 @@ -//--------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//--------------------------------------------------------------------------- - -#ifndef __WIN32_EXCEPTION_H_ -#define __WIN32_EXCEPTION_H_ -#pragma once - -#include -#include -#include - -#pragma warning(push, 4) - -//----------------------------------------------------------------------------- -// Class win32_exception -// -// Exception-based class used to wrap Windows system error codes - -class win32_exception : public std::exception -{ -public: - // Instance Constructors - // - win32_exception(DWORD code); - win32_exception(HRESULT code); - - // Copy Constructor - // - win32_exception(win32_exception const& rhs); - - // Move Constructor - // - win32_exception(win32_exception&& rhs); - - // char const* conversion operator - // - operator char const*() const; - - //------------------------------------------------------------------------- - // Member Functions - - // what (std::exception) - // - // Gets a pointer to the exception message text - virtual char const* what(void) const noexcept override; - -private: - //------------------------------------------------------------------------- - // Private Member Functions - - // format_message - // - // Generates the formatted message string from the project resources - static std::string format_message(DWORD result); - - //------------------------------------------------------------------------- - // Member Variables - - std::string m_what; // Win32 error message -}; - -//----------------------------------------------------------------------------- - -#pragma warning(pop) - -#endif // __WIN32_EXCEPTION_H_ +//--------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//--------------------------------------------------------------------------- + +#ifndef __WIN32_EXCEPTION_H_ +#define __WIN32_EXCEPTION_H_ +#pragma once + +#include +#include +#include + +#pragma warning(push, 4) + +//----------------------------------------------------------------------------- +// Class win32_exception +// +// Exception-based class used to wrap Windows system error codes + +class win32_exception : public std::exception +{ +public: + // Instance Constructors + // + win32_exception(DWORD code); + win32_exception(HRESULT code); + + // Copy Constructor + // + win32_exception(win32_exception const& rhs); + + // Move Constructor + // + win32_exception(win32_exception&& rhs); + + // char const* conversion operator + // + operator char const*() const; + + //------------------------------------------------------------------------- + // Member Functions + + // what (std::exception) + // + // Gets a pointer to the exception message text + virtual char const* what(void) const noexcept override; + +private: + //------------------------------------------------------------------------- + // Private Member Functions + + // format_message + // + // Generates the formatted message string from the project resources + static std::string format_message(DWORD result); + + //------------------------------------------------------------------------- + // Member Variables + + std::string m_what; // Win32 error message +}; + +//----------------------------------------------------------------------------- + +#pragma warning(pop) + +#endif // __WIN32_EXCEPTION_H_ diff --git a/src/wxstream.cpp b/src/wxstream.cpp index eabc648..14aaeec 100644 --- a/src/wxstream.cpp +++ b/src/wxstream.cpp @@ -1,564 +1,564 @@ -//--------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//--------------------------------------------------------------------------- - -#include "wxstream.h" - -#include "align.h" -#include "stdafx.h" -#include "string_exception.h" - -#include -#include -#include - -#pragma warning(push, 4) - -// wxstream::MAX_SAMPLE_QUEUE -// -// Maximum number of queued sample sets from the device -size_t const wxstream::MAX_SAMPLE_QUEUE = 200; // ~2sec - -// wxstream::STREAM_ID_AUDIO -// -// Stream identifier for the audio output stream -int const wxstream::STREAM_ID_AUDIO = 1; - -//--------------------------------------------------------------------------- -// wxstream Constructor (private) -// -// Arguments: -// -// device - RTL-SDR device instance -// tunerprops - Tuner device properties -// channelprops - Channel properties -// wxprops - Weather Radio digital signal processor properties - -wxstream::wxstream(std::unique_ptr device, - struct tunerprops const& tunerprops, - struct channelprops const& channelprops, - struct wxprops const& wxprops) - : m_device(std::move(device)), - m_muxname(generate_mux_name(channelprops)), - m_pcmsamplerate(wxprops.outputrate), - m_pcmgain(MPOW(10.0, (wxprops.outputgain / 10.0))) -{ - // The sample rate must be within 900001Hz - 3200000Hz - if ((wxprops.samplerate < 900001) || (wxprops.samplerate > 3200000)) - throw string_exception( - __func__, ": Tuner device sample rate must be in the range of 900001Hz to 3200000Hz"); - - // The only allowable output sample rates for this stream are 44100Hz and 48000Hz - if ((m_pcmsamplerate != 44100) && (m_pcmsamplerate != 48000)) - throw string_exception(__func__, - ": DSP output sample rate must be set to either 44.1KHz or 48.0KHz"); - - // Initialize the RTL-SDR device instance - m_device->set_frequency_correction(tunerprops.freqcorrection + channelprops.freqcorrection); - uint32_t samplerate = m_device->set_sample_rate(wxprops.samplerate); - uint32_t frequency = - m_device->set_center_frequency(channelprops.frequency + (samplerate / 4)); // DC offset - - // Initialize the demodulator parameters - // - tDemodInfo demodinfo = {}; - - demodinfo.HiCutmax = 100000; - demodinfo.HiCut = 5000; - demodinfo.LowCut = -5000; - demodinfo.SquelchValue = -160; - - // Initialize the narrowband FM demodulator - m_demodulator = std::unique_ptr(new CDemodulator()); - m_demodulator->SetInputSampleRate(static_cast(samplerate)); - m_demodulator->SetDemod(DEMOD_FM, demodinfo); - m_demodulator->SetDemodFreq(static_cast(frequency - channelprops.frequency)); - - // Initialize the output resampler - m_resampler = std::unique_ptr(new CFractResampler()); - m_resampler->Init(m_demodulator->GetInputBufferLimit()); - - // Adjust the device gain as specified by the channel properties - m_device->set_automatic_gain_control(channelprops.autogain); - if (channelprops.autogain == false) - m_device->set_gain(channelprops.manualgain); - - // Create a worker thread on which to perform the transfer operations - scalar_condition started{false}; - m_worker = std::thread(&wxstream::transfer, this, std::ref(started)); - started.wait_until_equals(true); -} - -//--------------------------------------------------------------------------- -// wxstream Destructor - -wxstream::~wxstream() -{ - close(); -} - -//--------------------------------------------------------------------------- -// wxstream::canseek -// -// Gets a flag indicating if the stream allows seek operations -// -// Arguments: -// -// NONE - -bool wxstream::canseek(void) const -{ - return false; -} - -//--------------------------------------------------------------------------- -// wxstream::close -// -// Closes the stream -// -// Arguments: -// -// NONE - -void wxstream::close(void) -{ - m_stop = true; // Signal worker thread to stop - if (m_device) - m_device->cancel_async(); // Cancel any async read operations - if (m_worker.joinable()) - m_worker.join(); // Wait for thread - m_device.reset(); // Release RTL-SDR device -} - -//--------------------------------------------------------------------------- -// wxstream::create (static) -// -// Factory method, creates a new wxstream instance -// -// Arguments: -// -// device - RTL-SDR device instance -// tunerprops - Tunder device properties -// channelprops - Channel properties -// wxprops - Weather Radio digital signal processor properties - -std::unique_ptr wxstream::create(std::unique_ptr device, - struct tunerprops const& tunerprops, - struct channelprops const& channelprops, - struct wxprops const& wxprops) -{ - return std::unique_ptr( - new wxstream(std::move(device), tunerprops, channelprops, wxprops)); -} - -//--------------------------------------------------------------------------- -// wxstream::demuxabort -// -// Aborts the demultiplexer -// -// Arguments: -// -// NONE - -void wxstream::demuxabort(void) -{ -} - -//--------------------------------------------------------------------------- -// wxstream::demuxflush -// -// Flushes the demultiplexer -// -// Arguments: -// -// NONE - -void wxstream::demuxflush(void) -{ -} - -//--------------------------------------------------------------------------- -// wxstream::demuxread -// -// Reads the next packet from the demultiplexer -// -// Arguments: -// -// allocator - DemuxPacket allocation function - -DEMUX_PACKET* wxstream::demuxread(std::function const& allocator) -{ - // Wait for there to be a packet of samples available for processing - std::unique_lock lock(m_queuelock); - m_cv.wait(lock, [&]() -> bool { return ((m_queue.size() > 0) || m_stopped.load() == true); }); - - // If the worker thread was stopped, check for and re-throw any exception that occurred, - // otherwise assume it was stopped normally and return an empty demultiplexer packet - if (m_stopped.load() == true) - { - - if (m_worker_exception) - std::rethrow_exception(m_worker_exception); - else - return allocator(0); - } - - // Pop off the topmost packet of samples from the queue<> and release the lock - std::unique_ptr insamples(std::move(m_queue.front())); - m_queue.pop(); - lock.unlock(); - - // If the packet of samples is null, the writer has indicated there was a problem - if (!insamples) - { - - m_dts = STREAM_TIME_BASE; // Reset the current decode time stamp - - // Create a STREAMCHANGE packet that has no data - DEMUX_PACKET* packet = allocator(0); - if (packet) - packet->iStreamId = DEMUX_SPECIALID_STREAMCHANGE; - - return packet; // Return the generated packet - } - - // Process the I/Q data - std::unique_ptr outsamples(new TYPEREAL[m_demodulator->GetInputBufferLimit()]); - int audiopackets = m_demodulator->ProcessData(m_demodulator->GetInputBufferLimit(), - insamples.get(), outsamples.get()); - - // Determine the size of the demultiplexer packet data and allocate it - int packetsize = audiopackets * sizeof(TYPEMONO16); - DEMUX_PACKET* packet = allocator(packetsize); - if (packet == nullptr) - return nullptr; - - // Resample the audio data directly into the allocated packet buffer - audiopackets = m_resampler->Resample( - audiopackets, (m_demodulator->GetOutputRate() / m_pcmsamplerate), outsamples.get(), - reinterpret_cast(packet->pData), m_pcmgain); - - // Calculate the proper duration for the packet - double duration = (audiopackets / static_cast(m_pcmsamplerate)) * STREAM_TIME_BASE; - - // Set up the demultiplexer packet with the proper size, duration and dts - packet->iStreamId = STREAM_ID_AUDIO; - packet->iSize = audiopackets * sizeof(TYPEMONO16); - packet->duration = duration; - packet->dts = packet->pts = m_dts; - - // Increment the decode time stamp value based on the calculated duration - m_dts += duration; - - return packet; -} - -//--------------------------------------------------------------------------- -// wxstream::demuxreset -// -// Resets the demultiplexer -// -// Arguments: -// -// NONE - -void wxstream::demuxreset(void) -{ -} - -//--------------------------------------------------------------------------- -// wxstream::devicename -// -// Gets the device name associated with the stream -// -// Arguments: -// -// NONE - -std::string wxstream::devicename(void) const -{ - return std::string(m_device->get_device_name()); -} - -//--------------------------------------------------------------------------- -// wxstream::enumproperties -// -// Enumerates the stream properties -// -// Arguments: -// -// callback - Callback to invoke for each stream - -void wxstream::enumproperties(std::function const& callback) -{ - // AUDIO STREAM - // - streamprops audio = {}; - audio.codec = "pcm_s16le"; - audio.pid = STREAM_ID_AUDIO; - audio.channels = 1; - audio.samplerate = static_cast(m_pcmsamplerate); - audio.bitspersample = 16; - callback(audio); -} - -//--------------------------------------------------------------------------- -// wxstream::generate_mux_name (private) -// -// Generates the mux name to associate with the stream -// -// Arguments: -// -// channelprops - Channel properties structure - -std::string wxstream::generate_mux_name(struct channelprops const& channelprops) const -{ - // Use "WX1", "WX2", "WX3" if the frequency matches one of those channel designations - if (channelprops.frequency == 162550000) - return std::string("WX1"); - else if (channelprops.frequency == 162400000) - return std::string("WX2"); - else if (channelprops.frequency == 162475000) - return std::string("WX3"); - else if (channelprops.frequency == 162425000) - return std::string("WX4"); - else if (channelprops.frequency == 162450000) - return std::string("WX5"); - else if (channelprops.frequency == 162500000) - return std::string("WX6"); - else if (channelprops.frequency == 162525000) - return std::string("WX7"); - - // Otherwise use the channel frequency in Megahertz - char buf[64] = {0}; - snprintf(buf, std::extent::value, "%.3f VHF", - (static_cast(channelprops.frequency) / 1000000.0f)); - return std::string(buf); -} - -//--------------------------------------------------------------------------- -// wxstream::length -// -// Gets the length of the stream; or -1 if stream is real-time -// -// Arguments: -// -// NONE - -long long wxstream::length(void) const -{ - return -1; -} - -//--------------------------------------------------------------------------- -// wxstream::muxname -// -// Gets the mux name associated with the stream -// -// Arguments: -// -// NONE - -std::string wxstream::muxname(void) const -{ - return m_muxname; -} - -//--------------------------------------------------------------------------- -// wxstream::position -// -// Gets the current position of the stream -// -// Arguments: -// -// NONE - -long long wxstream::position(void) const -{ - return -1; -} - -//--------------------------------------------------------------------------- -// wxstream::read -// -// Reads data from the live stream -// -// Arguments: -// -// buffer - Buffer to receive the live stream data -// count - Size of the destination buffer in bytes - -size_t wxstream::read(uint8_t* /*buffer*/, size_t /*count*/) -{ - return 0; -} - -//--------------------------------------------------------------------------- -// wxstream::realtime -// -// Gets a flag indicating if the stream is real-time -// -// Arguments: -// -// NONE - -bool wxstream::realtime(void) const -{ - return true; -} - -//--------------------------------------------------------------------------- -// wxstream::seek -// -// Sets the stream pointer to a specific position -// -// Arguments: -// -// position - Delta within the stream to seek, relative to whence -// whence - Starting position from which to apply the delta - -long long wxstream::seek(long long /*position*/, int /*whence*/) -{ - return -1; -} - -//--------------------------------------------------------------------------- -// wxstream::servicename -// -// Gets the service name associated with the stream -// -// Arguments: -// -// NONE - -std::string wxstream::servicename(void) const -{ - return std::string("Narrowband FM VHF radio"); -} - -//--------------------------------------------------------------------------- -// wxstream::signalquality -// -// Gets the signal quality as percentages -// -// Arguments: -// -// NONE - -void wxstream::signalquality(int& quality, int& snr) const -{ - TYPEREAL demodquality = 0; - TYPEREAL demodsnr = 0; - - m_demodulator->GetSignalLevels(demodquality, demodsnr); - - // The levels are expressed in the range [0,1] - quality = std::max(0, std::min(100, static_cast(100.0 * demodquality))); - snr = std::max(0, std::min(100, static_cast(100.0 * demodsnr))); -} - -//--------------------------------------------------------------------------- -// wxstream::transfer (private) -// -// Worker thread procedure used to transfer data into the ring buffer -// -// Arguments: -// -// started - Condition variable to set when thread has started - -void wxstream::transfer(scalar_condition& started) -{ - assert(m_demodulator); - assert(m_device); - - // The I/Q samples from the device come in as a pair of 8 bit unsigned integers - size_t const readsize = m_demodulator->GetInputBufferLimit() * 2; - - // read_callback_func (local) - // - // Asynchronous read callback function for the RTL-SDR device - auto read_callback_func = [&](uint8_t const* buffer, size_t count) -> void - { - std::unique_ptr samples; // Array of I/Q samples to return - - // If the proper amount of data was returned by the callback, convert it into - // the floating-point I/Q sample data for the demodulator to process - if (count == readsize) - { - - samples = std::unique_ptr(new TYPECPX[readsize]); - for (int index = 0; index < m_demodulator->GetInputBufferLimit(); index++) - { - - // The demodulator expects the I/Q samples in the range of -32767.0 through +32767.0 - // (32767.0 / 127.5) = 256.9960784313725 - samples[index] = { - -#ifdef FMDSP_USE_DOUBLE_PRECISION - (static_cast(buffer[(index * 2)]) - 127.5) * 256.9960784313725, // I - (static_cast(buffer[(index * 2) + 1]) - 127.5) * 256.9960784313725, // Q -#else - (static_cast(buffer[(index * 2)]) - 127.5f) * 256.9960784313725f, // I - (static_cast(buffer[(index * 2) + 1]) - 127.5f) * 256.9960784313725f, // Q -#endif - }; - } - } - - // Push the converted samples into the queue<> for processing. If there is insufficient space - // left in the queue<>, the samples aren't being processed quickly enough to keep up with the rate - std::unique_lock lock(m_queuelock); - if (m_queue.size() < MAX_SAMPLE_QUEUE) - m_queue.emplace(std::move(samples)); - else - { - - m_queue = sample_queue_t(); // Replace the queue<> - m_queue.push(nullptr); // Push a resync packet (null) - if (samples) - m_queue.emplace(std::move(samples)); // Push samples - } - - // Notify any threads waiting on the lock that the queue<> has been updated - m_cv.notify_all(); - }; - - // Begin streaming from the device and inform the caller that the thread is running - m_device->begin_stream(); - started = true; - - // Continuously read data from the device until cancel_async() has been called - try - { - m_device->read_async(read_callback_func, static_cast(readsize)); - } - catch (...) - { - m_worker_exception = std::current_exception(); - } - - m_stopped.store(true); // Thread is stopped - m_cv.notify_all(); // Unblock any waiters -} - -//--------------------------------------------------------------------------- - -#pragma warning(pop) +//--------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//--------------------------------------------------------------------------- + +#include "wxstream.h" + +#include "align.h" +#include "stdafx.h" +#include "string_exception.h" + +#include +#include +#include + +#pragma warning(push, 4) + +// wxstream::MAX_SAMPLE_QUEUE +// +// Maximum number of queued sample sets from the device +size_t const wxstream::MAX_SAMPLE_QUEUE = 200; // ~2sec + +// wxstream::STREAM_ID_AUDIO +// +// Stream identifier for the audio output stream +int const wxstream::STREAM_ID_AUDIO = 1; + +//--------------------------------------------------------------------------- +// wxstream Constructor (private) +// +// Arguments: +// +// device - RTL-SDR device instance +// tunerprops - Tuner device properties +// channelprops - Channel properties +// wxprops - Weather Radio digital signal processor properties + +wxstream::wxstream(std::unique_ptr device, + struct tunerprops const& tunerprops, + struct channelprops const& channelprops, + struct wxprops const& wxprops) + : m_device(std::move(device)), + m_muxname(generate_mux_name(channelprops)), + m_pcmsamplerate(wxprops.outputrate), + m_pcmgain(MPOW(10.0, (wxprops.outputgain / 10.0))) +{ + // The sample rate must be within 900001Hz - 3200000Hz + if ((wxprops.samplerate < 900001) || (wxprops.samplerate > 3200000)) + throw string_exception( + __func__, ": Tuner device sample rate must be in the range of 900001Hz to 3200000Hz"); + + // The only allowable output sample rates for this stream are 44100Hz and 48000Hz + if ((m_pcmsamplerate != 44100) && (m_pcmsamplerate != 48000)) + throw string_exception(__func__, + ": DSP output sample rate must be set to either 44.1KHz or 48.0KHz"); + + // Initialize the RTL-SDR device instance + m_device->set_frequency_correction(tunerprops.freqcorrection + channelprops.freqcorrection); + uint32_t samplerate = m_device->set_sample_rate(wxprops.samplerate); + uint32_t frequency = + m_device->set_center_frequency(channelprops.frequency + (samplerate / 4)); // DC offset + + // Initialize the demodulator parameters + // + tDemodInfo demodinfo = {}; + + demodinfo.HiCutmax = 100000; + demodinfo.HiCut = 5000; + demodinfo.LowCut = -5000; + demodinfo.SquelchValue = -160; + + // Initialize the narrowband FM demodulator + m_demodulator = std::unique_ptr(new CDemodulator()); + m_demodulator->SetInputSampleRate(static_cast(samplerate)); + m_demodulator->SetDemod(DEMOD_FM, demodinfo); + m_demodulator->SetDemodFreq(static_cast(frequency - channelprops.frequency)); + + // Initialize the output resampler + m_resampler = std::unique_ptr(new CFractResampler()); + m_resampler->Init(m_demodulator->GetInputBufferLimit()); + + // Adjust the device gain as specified by the channel properties + m_device->set_automatic_gain_control(channelprops.autogain); + if (channelprops.autogain == false) + m_device->set_gain(channelprops.manualgain); + + // Create a worker thread on which to perform the transfer operations + scalar_condition started{false}; + m_worker = std::thread(&wxstream::transfer, this, std::ref(started)); + started.wait_until_equals(true); +} + +//--------------------------------------------------------------------------- +// wxstream Destructor + +wxstream::~wxstream() +{ + close(); +} + +//--------------------------------------------------------------------------- +// wxstream::canseek +// +// Gets a flag indicating if the stream allows seek operations +// +// Arguments: +// +// NONE + +bool wxstream::canseek(void) const +{ + return false; +} + +//--------------------------------------------------------------------------- +// wxstream::close +// +// Closes the stream +// +// Arguments: +// +// NONE + +void wxstream::close(void) +{ + m_stop = true; // Signal worker thread to stop + if (m_device) + m_device->cancel_async(); // Cancel any async read operations + if (m_worker.joinable()) + m_worker.join(); // Wait for thread + m_device.reset(); // Release RTL-SDR device +} + +//--------------------------------------------------------------------------- +// wxstream::create (static) +// +// Factory method, creates a new wxstream instance +// +// Arguments: +// +// device - RTL-SDR device instance +// tunerprops - Tunder device properties +// channelprops - Channel properties +// wxprops - Weather Radio digital signal processor properties + +std::unique_ptr wxstream::create(std::unique_ptr device, + struct tunerprops const& tunerprops, + struct channelprops const& channelprops, + struct wxprops const& wxprops) +{ + return std::unique_ptr( + new wxstream(std::move(device), tunerprops, channelprops, wxprops)); +} + +//--------------------------------------------------------------------------- +// wxstream::demuxabort +// +// Aborts the demultiplexer +// +// Arguments: +// +// NONE + +void wxstream::demuxabort(void) +{ +} + +//--------------------------------------------------------------------------- +// wxstream::demuxflush +// +// Flushes the demultiplexer +// +// Arguments: +// +// NONE + +void wxstream::demuxflush(void) +{ +} + +//--------------------------------------------------------------------------- +// wxstream::demuxread +// +// Reads the next packet from the demultiplexer +// +// Arguments: +// +// allocator - DemuxPacket allocation function + +DEMUX_PACKET* wxstream::demuxread(std::function const& allocator) +{ + // Wait for there to be a packet of samples available for processing + std::unique_lock lock(m_queuelock); + m_cv.wait(lock, [&]() -> bool { return ((m_queue.size() > 0) || m_stopped.load() == true); }); + + // If the worker thread was stopped, check for and re-throw any exception that occurred, + // otherwise assume it was stopped normally and return an empty demultiplexer packet + if (m_stopped.load() == true) + { + + if (m_worker_exception) + std::rethrow_exception(m_worker_exception); + else + return allocator(0); + } + + // Pop off the topmost packet of samples from the queue<> and release the lock + std::unique_ptr insamples(std::move(m_queue.front())); + m_queue.pop(); + lock.unlock(); + + // If the packet of samples is null, the writer has indicated there was a problem + if (!insamples) + { + + m_dts = STREAM_TIME_BASE; // Reset the current decode time stamp + + // Create a STREAMCHANGE packet that has no data + DEMUX_PACKET* packet = allocator(0); + if (packet) + packet->iStreamId = DEMUX_SPECIALID_STREAMCHANGE; + + return packet; // Return the generated packet + } + + // Process the I/Q data + std::unique_ptr outsamples(new TYPEREAL[m_demodulator->GetInputBufferLimit()]); + int audiopackets = m_demodulator->ProcessData(m_demodulator->GetInputBufferLimit(), + insamples.get(), outsamples.get()); + + // Determine the size of the demultiplexer packet data and allocate it + int packetsize = audiopackets * sizeof(TYPEMONO16); + DEMUX_PACKET* packet = allocator(packetsize); + if (packet == nullptr) + return nullptr; + + // Resample the audio data directly into the allocated packet buffer + audiopackets = m_resampler->Resample( + audiopackets, (m_demodulator->GetOutputRate() / m_pcmsamplerate), outsamples.get(), + reinterpret_cast(packet->pData), m_pcmgain); + + // Calculate the proper duration for the packet + double duration = (audiopackets / static_cast(m_pcmsamplerate)) * STREAM_TIME_BASE; + + // Set up the demultiplexer packet with the proper size, duration and dts + packet->iStreamId = STREAM_ID_AUDIO; + packet->iSize = audiopackets * sizeof(TYPEMONO16); + packet->duration = duration; + packet->dts = packet->pts = m_dts; + + // Increment the decode time stamp value based on the calculated duration + m_dts += duration; + + return packet; +} + +//--------------------------------------------------------------------------- +// wxstream::demuxreset +// +// Resets the demultiplexer +// +// Arguments: +// +// NONE + +void wxstream::demuxreset(void) +{ +} + +//--------------------------------------------------------------------------- +// wxstream::devicename +// +// Gets the device name associated with the stream +// +// Arguments: +// +// NONE + +std::string wxstream::devicename(void) const +{ + return std::string(m_device->get_device_name()); +} + +//--------------------------------------------------------------------------- +// wxstream::enumproperties +// +// Enumerates the stream properties +// +// Arguments: +// +// callback - Callback to invoke for each stream + +void wxstream::enumproperties(std::function const& callback) +{ + // AUDIO STREAM + // + streamprops audio = {}; + audio.codec = "pcm_s16le"; + audio.pid = STREAM_ID_AUDIO; + audio.channels = 1; + audio.samplerate = static_cast(m_pcmsamplerate); + audio.bitspersample = 16; + callback(audio); +} + +//--------------------------------------------------------------------------- +// wxstream::generate_mux_name (private) +// +// Generates the mux name to associate with the stream +// +// Arguments: +// +// channelprops - Channel properties structure + +std::string wxstream::generate_mux_name(struct channelprops const& channelprops) const +{ + // Use "WX1", "WX2", "WX3" if the frequency matches one of those channel designations + if (channelprops.frequency == 162550000) + return std::string("WX1"); + else if (channelprops.frequency == 162400000) + return std::string("WX2"); + else if (channelprops.frequency == 162475000) + return std::string("WX3"); + else if (channelprops.frequency == 162425000) + return std::string("WX4"); + else if (channelprops.frequency == 162450000) + return std::string("WX5"); + else if (channelprops.frequency == 162500000) + return std::string("WX6"); + else if (channelprops.frequency == 162525000) + return std::string("WX7"); + + // Otherwise use the channel frequency in Megahertz + char buf[64] = {0}; + snprintf(buf, std::extent::value, "%.3f VHF", + (static_cast(channelprops.frequency) / 1000000.0f)); + return std::string(buf); +} + +//--------------------------------------------------------------------------- +// wxstream::length +// +// Gets the length of the stream; or -1 if stream is real-time +// +// Arguments: +// +// NONE + +long long wxstream::length(void) const +{ + return -1; +} + +//--------------------------------------------------------------------------- +// wxstream::muxname +// +// Gets the mux name associated with the stream +// +// Arguments: +// +// NONE + +std::string wxstream::muxname(void) const +{ + return m_muxname; +} + +//--------------------------------------------------------------------------- +// wxstream::position +// +// Gets the current position of the stream +// +// Arguments: +// +// NONE + +long long wxstream::position(void) const +{ + return -1; +} + +//--------------------------------------------------------------------------- +// wxstream::read +// +// Reads data from the live stream +// +// Arguments: +// +// buffer - Buffer to receive the live stream data +// count - Size of the destination buffer in bytes + +size_t wxstream::read(uint8_t* /*buffer*/, size_t /*count*/) +{ + return 0; +} + +//--------------------------------------------------------------------------- +// wxstream::realtime +// +// Gets a flag indicating if the stream is real-time +// +// Arguments: +// +// NONE + +bool wxstream::realtime(void) const +{ + return true; +} + +//--------------------------------------------------------------------------- +// wxstream::seek +// +// Sets the stream pointer to a specific position +// +// Arguments: +// +// position - Delta within the stream to seek, relative to whence +// whence - Starting position from which to apply the delta + +long long wxstream::seek(long long /*position*/, int /*whence*/) +{ + return -1; +} + +//--------------------------------------------------------------------------- +// wxstream::servicename +// +// Gets the service name associated with the stream +// +// Arguments: +// +// NONE + +std::string wxstream::servicename(void) const +{ + return std::string("Narrowband FM VHF radio"); +} + +//--------------------------------------------------------------------------- +// wxstream::signalquality +// +// Gets the signal quality as percentages +// +// Arguments: +// +// NONE + +void wxstream::signalquality(int& quality, int& snr) const +{ + TYPEREAL demodquality = 0; + TYPEREAL demodsnr = 0; + + m_demodulator->GetSignalLevels(demodquality, demodsnr); + + // The levels are expressed in the range [0,1] + quality = std::max(0, std::min(100, static_cast(100.0 * demodquality))); + snr = std::max(0, std::min(100, static_cast(100.0 * demodsnr))); +} + +//--------------------------------------------------------------------------- +// wxstream::transfer (private) +// +// Worker thread procedure used to transfer data into the ring buffer +// +// Arguments: +// +// started - Condition variable to set when thread has started + +void wxstream::transfer(scalar_condition& started) +{ + assert(m_demodulator); + assert(m_device); + + // The I/Q samples from the device come in as a pair of 8 bit unsigned integers + size_t const readsize = m_demodulator->GetInputBufferLimit() * 2; + + // read_callback_func (local) + // + // Asynchronous read callback function for the RTL-SDR device + auto read_callback_func = [&](uint8_t const* buffer, size_t count) -> void + { + std::unique_ptr samples; // Array of I/Q samples to return + + // If the proper amount of data was returned by the callback, convert it into + // the floating-point I/Q sample data for the demodulator to process + if (count == readsize) + { + + samples = std::unique_ptr(new TYPECPX[readsize]); + for (int index = 0; index < m_demodulator->GetInputBufferLimit(); index++) + { + + // The demodulator expects the I/Q samples in the range of -32767.0 through +32767.0 + // (32767.0 / 127.5) = 256.9960784313725 + samples[index] = { + +#ifdef FMDSP_USE_DOUBLE_PRECISION + (static_cast(buffer[(index * 2)]) - 127.5) * 256.9960784313725, // I + (static_cast(buffer[(index * 2) + 1]) - 127.5) * 256.9960784313725, // Q +#else + (static_cast(buffer[(index * 2)]) - 127.5f) * 256.9960784313725f, // I + (static_cast(buffer[(index * 2) + 1]) - 127.5f) * 256.9960784313725f, // Q +#endif + }; + } + } + + // Push the converted samples into the queue<> for processing. If there is insufficient space + // left in the queue<>, the samples aren't being processed quickly enough to keep up with the rate + std::unique_lock lock(m_queuelock); + if (m_queue.size() < MAX_SAMPLE_QUEUE) + m_queue.emplace(std::move(samples)); + else + { + + m_queue = sample_queue_t(); // Replace the queue<> + m_queue.push(nullptr); // Push a resync packet (null) + if (samples) + m_queue.emplace(std::move(samples)); // Push samples + } + + // Notify any threads waiting on the lock that the queue<> has been updated + m_cv.notify_all(); + }; + + // Begin streaming from the device and inform the caller that the thread is running + m_device->begin_stream(); + started = true; + + // Continuously read data from the device until cancel_async() has been called + try + { + m_device->read_async(read_callback_func, static_cast(readsize)); + } + catch (...) + { + m_worker_exception = std::current_exception(); + } + + m_stopped.store(true); // Thread is stopped + m_cv.notify_all(); // Unblock any waiters +} + +//--------------------------------------------------------------------------- + +#pragma warning(pop) diff --git a/src/wxstream.h b/src/wxstream.h index 152b7fd..634c34c 100644 --- a/src/wxstream.h +++ b/src/wxstream.h @@ -1,221 +1,221 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Michael G. Brehm -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -//----------------------------------------------------------------------------- - -#ifndef __WXSTREAM_H_ -#define __WXSTREAM_H_ -#pragma once - -#include "fmdsp/demodulator.h" -#include "fmdsp/fractresampler.h" -#include "props.h" -#include "pvrstream.h" -#include "rtldevice.h" -#include "scalar_condition.h" - -#include -#include -#include -#include -#include -#include - -#pragma warning(push, 4) - -//--------------------------------------------------------------------------- -// Class wxstream -// -// Implements a Weather Radio stream - -class wxstream : public pvrstream -{ -public: - // Destructor - // - virtual ~wxstream(); - - //----------------------------------------------------------------------- - // Member Functions - - // canseek - // - // Flag indicating if the stream allows seek operations - bool canseek(void) const override; - - // close - // - // Closes the stream - void close(void) override; - - // create (static) - // - // Factory method, creates a new wxstream instance - static std::unique_ptr create(std::unique_ptr device, - struct tunerprops const& tunerprops, - struct channelprops const& channelprops, - struct wxprops const& wxprops); - - // demuxabort - // - // Aborts the demultiplexer - void demuxabort(void) override; - - // demuxflush - // - // Flushes the demultiplexer - void demuxflush(void) override; - - // demuxread - // - // Reads the next packet from the demultiplexer - DEMUX_PACKET* demuxread(std::function const& allocator) override; - - // demuxreset - // - // Resets the demultiplexer - void demuxreset(void) override; - - // devicename - // - // Gets the device name associated with the stream - std::string devicename(void) const override; - - // enumproperties - // - // Enumerates the stream properties - void enumproperties( - std::function const& callback) override; - - // length - // - // Gets the length of the stream - long long length(void) const override; - - // muxname - // - // Gets the mux name associated with the stream - std::string muxname(void) const override; - - // position - // - // Gets the current position of the stream - long long position(void) const override; - - // read - // - // Reads available data from the stream - size_t read(uint8_t* buffer, size_t count) override; - - // realtime - // - // Gets a flag indicating if the stream is real-time - bool realtime(void) const override; - - // seek - // - // Sets the stream pointer to a specific position - long long seek(long long position, int whence) override; - - // servicename - // - // Gets the service name associated with the stream - std::string servicename(void) const override; - - // signalquality - // - // Gets the signal quality as percentages - void signalquality(int& quality, int& snr) const override; - -private: - wxstream(wxstream const&) = delete; - wxstream& operator=(wxstream const&) = delete; - - // MAX_SAMPLE_QUEUE - // - // Maximum number of queued sample sets from device - static size_t const MAX_SAMPLE_QUEUE; - - // STREAM_ID_AUDIO - // - // Stream identifier for the audio output stream - static int const STREAM_ID_AUDIO; - - // Instance Constructor - // - wxstream(std::unique_ptr device, - struct tunerprops const& tunerprops, - struct channelprops const& channelprops, - struct wxprops const& wxprops); - - //----------------------------------------------------------------------- - // Private Type Declarations - - // sample_queue_item_t - // - // Defines the type of a single sample_queue_t entry - using sample_queue_item_t = std::unique_ptr; - - // sample_queue_t - // - // Defines the type of the input sample queue - using sample_queue_t = std::queue; - - //----------------------------------------------------------------------- - // Private Member Functions - - // generate_mux_name - // - // Generates the mux name to associate with the stream - std::string generate_mux_name(struct channelprops const& channelprops) const; - - // transfer - // - // Worker thread procedure used to transfer data into the ring buffer - void transfer(scalar_condition& started); - - //----------------------------------------------------------------------- - // Member Variables - - std::unique_ptr m_device; // RTL-SDR device instance - std::unique_ptr m_demodulator; // CuteSDR demodulator instance - std::unique_ptr m_resampler; // CuteSDR resampler instance - - std::string const m_muxname; // Generated mux name - uint32_t const m_pcmsamplerate; // Output sample rate - TYPEREAL const m_pcmgain; // Output gain - double m_dts{STREAM_TIME_BASE}; // Current decode time stamp - - // STREAM CONTROL - // - sample_queue_t m_queue; // queue<> of prepared samples - mutable std::mutex m_queuelock; // Synchronization object - std::condition_variable m_cv; // Transfer event condvar - std::thread m_worker; // Data transfer thread - std::exception_ptr m_worker_exception; // Exception on worker thread - scalar_condition m_stop{false}; // Condition to stop data transfer - std::atomic m_stopped{false}; // Data transfer stopped flag -}; - -//----------------------------------------------------------------------------- - -#pragma warning(pop) - -#endif // __WXSTREAM_H_ +//----------------------------------------------------------------------------- +// Copyright (c) 2020-2022 Michael G. Brehm +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef __WXSTREAM_H_ +#define __WXSTREAM_H_ +#pragma once + +#include "fmdsp/demodulator.h" +#include "fmdsp/fractresampler.h" +#include "props.h" +#include "pvrstream.h" +#include "rtldevice.h" +#include "scalar_condition.h" + +#include +#include +#include +#include +#include +#include + +#pragma warning(push, 4) + +//--------------------------------------------------------------------------- +// Class wxstream +// +// Implements a Weather Radio stream + +class wxstream : public pvrstream +{ +public: + // Destructor + // + virtual ~wxstream(); + + //----------------------------------------------------------------------- + // Member Functions + + // canseek + // + // Flag indicating if the stream allows seek operations + bool canseek(void) const override; + + // close + // + // Closes the stream + void close(void) override; + + // create (static) + // + // Factory method, creates a new wxstream instance + static std::unique_ptr create(std::unique_ptr device, + struct tunerprops const& tunerprops, + struct channelprops const& channelprops, + struct wxprops const& wxprops); + + // demuxabort + // + // Aborts the demultiplexer + void demuxabort(void) override; + + // demuxflush + // + // Flushes the demultiplexer + void demuxflush(void) override; + + // demuxread + // + // Reads the next packet from the demultiplexer + DEMUX_PACKET* demuxread(std::function const& allocator) override; + + // demuxreset + // + // Resets the demultiplexer + void demuxreset(void) override; + + // devicename + // + // Gets the device name associated with the stream + std::string devicename(void) const override; + + // enumproperties + // + // Enumerates the stream properties + void enumproperties( + std::function const& callback) override; + + // length + // + // Gets the length of the stream + long long length(void) const override; + + // muxname + // + // Gets the mux name associated with the stream + std::string muxname(void) const override; + + // position + // + // Gets the current position of the stream + long long position(void) const override; + + // read + // + // Reads available data from the stream + size_t read(uint8_t* buffer, size_t count) override; + + // realtime + // + // Gets a flag indicating if the stream is real-time + bool realtime(void) const override; + + // seek + // + // Sets the stream pointer to a specific position + long long seek(long long position, int whence) override; + + // servicename + // + // Gets the service name associated with the stream + std::string servicename(void) const override; + + // signalquality + // + // Gets the signal quality as percentages + void signalquality(int& quality, int& snr) const override; + +private: + wxstream(wxstream const&) = delete; + wxstream& operator=(wxstream const&) = delete; + + // MAX_SAMPLE_QUEUE + // + // Maximum number of queued sample sets from device + static size_t const MAX_SAMPLE_QUEUE; + + // STREAM_ID_AUDIO + // + // Stream identifier for the audio output stream + static int const STREAM_ID_AUDIO; + + // Instance Constructor + // + wxstream(std::unique_ptr device, + struct tunerprops const& tunerprops, + struct channelprops const& channelprops, + struct wxprops const& wxprops); + + //----------------------------------------------------------------------- + // Private Type Declarations + + // sample_queue_item_t + // + // Defines the type of a single sample_queue_t entry + using sample_queue_item_t = std::unique_ptr; + + // sample_queue_t + // + // Defines the type of the input sample queue + using sample_queue_t = std::queue; + + //----------------------------------------------------------------------- + // Private Member Functions + + // generate_mux_name + // + // Generates the mux name to associate with the stream + std::string generate_mux_name(struct channelprops const& channelprops) const; + + // transfer + // + // Worker thread procedure used to transfer data into the ring buffer + void transfer(scalar_condition& started); + + //----------------------------------------------------------------------- + // Member Variables + + std::unique_ptr m_device; // RTL-SDR device instance + std::unique_ptr m_demodulator; // CuteSDR demodulator instance + std::unique_ptr m_resampler; // CuteSDR resampler instance + + std::string const m_muxname; // Generated mux name + uint32_t const m_pcmsamplerate; // Output sample rate + TYPEREAL const m_pcmgain; // Output gain + double m_dts{STREAM_TIME_BASE}; // Current decode time stamp + + // STREAM CONTROL + // + sample_queue_t m_queue; // queue<> of prepared samples + mutable std::mutex m_queuelock; // Synchronization object + std::condition_variable m_cv; // Transfer event condvar + std::thread m_worker; // Data transfer thread + std::exception_ptr m_worker_exception; // Exception on worker thread + scalar_condition m_stop{false}; // Condition to stop data transfer + std::atomic m_stopped{false}; // Data transfer stopped flag +}; + +//----------------------------------------------------------------------------- + +#pragma warning(pop) + +#endif // __WXSTREAM_H_