From 8e2342018ad2215ebd273fa4ad69d04fed514d2c Mon Sep 17 00:00:00 2001 From: charlie-foxtrot <13514783+charlie-foxtrot@users.noreply.github.com> Date: Tue, 6 Feb 2024 19:23:06 -0800 Subject: [PATCH 1/9] add clang formatting workflow --- .clang-format | 5 + .devcontainer/Dockerfile.ubuntu-20.04 | 3 +- .devcontainer/Dockerfile.ubuntu-latest | 3 +- .devcontainer/devcontainer.json | 3 +- .github/workflows/code_formatting.yml | 16 ++ src/ctcss.cpp | 212 ++++++++++++------------- 6 files changed, 127 insertions(+), 115 deletions(-) create mode 100644 .clang-format create mode 100644 .github/workflows/code_formatting.yml diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..90fb419 --- /dev/null +++ b/.clang-format @@ -0,0 +1,5 @@ +--- +BasedOnStyle: Chromium +IndentWidth: 4 +ObjCBlockIndentWidth: 4 +ColumnLimit: 200 \ No newline at end of file diff --git a/.devcontainer/Dockerfile.ubuntu-20.04 b/.devcontainer/Dockerfile.ubuntu-20.04 index eecf022..59d94a9 100644 --- a/.devcontainer/Dockerfile.ubuntu-20.04 +++ b/.devcontainer/Dockerfile.ubuntu-20.04 @@ -10,7 +10,8 @@ RUN DEBIAN_FRONTEND=noninteractive \ tzdata\ git \ sudo \ - gdb + gdb \ + clang-format WORKDIR /app COPY .github/install_dependencies /app/ diff --git a/.devcontainer/Dockerfile.ubuntu-latest b/.devcontainer/Dockerfile.ubuntu-latest index 29b1524..3a60736 100644 --- a/.devcontainer/Dockerfile.ubuntu-latest +++ b/.devcontainer/Dockerfile.ubuntu-latest @@ -10,7 +10,8 @@ RUN DEBIAN_FRONTEND=noninteractive \ tzdata\ git \ sudo \ - gdb + gdb \ + clang-format WORKDIR /app COPY .github/install_dependencies /app/ diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 580cc00..5faa346 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -22,7 +22,8 @@ "twxs.cmake", "streetsidesoftware.code-spell-checker", "ms-azuretools.vscode-docker", - "GitHub.vscode-github-actions" + "GitHub.vscode-github-actions", + "xaver.clang-format" ] } }, diff --git a/.github/workflows/code_formatting.yml b/.github/workflows/code_formatting.yml new file mode 100644 index 0000000..328c883 --- /dev/null +++ b/.github/workflows/code_formatting.yml @@ -0,0 +1,16 @@ +name: Code Formatting + +on: + pull_request: + schedule: + - cron: '39 13 * * *' # run daily + +jobs: + code_formatting: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: RafikFarhad/clang-format-github-action@v3 + with: + sources: "src/**/*.h,src/**/*.cpp" + style: "file" diff --git a/src/ctcss.cpp b/src/ctcss.cpp index dee5e57..7a3cdbd 100644 --- a/src/ctcss.cpp +++ b/src/ctcss.cpp @@ -17,10 +17,10 @@ * along with this program. If not, see . */ -#include // M_PI -#include // sort +#include // M_PI +#include // sort -#include "logging.h" // debug_print() +#include "logging.h" // debug_print() #include "ctcss.h" @@ -28,157 +28,145 @@ using namespace std; // Implementation of https://www.embedded.com/detecting-ctcss-tones-with-goertzels-algorithm/ // also https://www.embedded.com/the-goertzel-algorithm/ -ToneDetector::ToneDetector(float tone_freq, float sample_rate, int window_size) -{ - tone_freq_ = tone_freq; - magnitude_ = 0.0; - - window_size_ = window_size; - - int k = (0.5 + window_size * tone_freq / sample_rate); - float omega = (2.0 * M_PI * k) / window_size; - coeff_ = 2.0 * cos(omega); - - reset(); +ToneDetector::ToneDetector(float tone_freq, float sample_rate, int window_size) { + tone_freq_ = tone_freq; + magnitude_ = 0.0; + + window_size_ = window_size; + + int k = (0.5 + window_size * tone_freq / sample_rate); + float omega = (2.0 * M_PI * k) / window_size; + coeff_ = 2.0 * cos(omega); + + reset(); } -void ToneDetector::process_sample(const float &sample) { - q0_ = coeff_ * q1_ - q2_ + sample; - q2_ = q1_; - q1_ = q0_; +void ToneDetector::process_sample(const float& sample) { + q0_ = coeff_ * q1_ - q2_ + sample; + q2_ = q1_; + q1_ = q0_; - count_++; - if (count_ == window_size_) { - magnitude_ = q1_*q1_ + q2_*q2_ - q1_*q2_*coeff_; - count_ = 0; - } + count_++; + if (count_ == window_size_) { + magnitude_ = q1_ * q1_ + q2_ * q2_ - q1_ * q2_ * coeff_; + count_ = 0; + } } void ToneDetector::reset(void) { - count_ = 0; - q0_ = q1_ = q2_ = 0.0; + count_ = 0; + q0_ = q1_ = q2_ = 0.0; } +bool ToneDetectorSet::add(const float& tone_freq, const float& sample_rate, int window_size) { + ToneDetector new_tone = ToneDetector(tone_freq, sample_rate, window_size); + for (const auto tone : tones_) { + if (new_tone.coefficient() == tone.coefficient()) { + debug_print("Skipping tone %f, too close to other tones\n", tone_freq); + return false; + } + } -bool ToneDetectorSet::add(const float & tone_freq, const float & sample_rate, int window_size) { - ToneDetector new_tone = ToneDetector(tone_freq, sample_rate, window_size); - - for (const auto tone : tones_) { - if (new_tone.coefficient() == tone.coefficient()) { - debug_print("Skipping tone %f, too close to other tones\n", tone_freq); - return false; - } - } - - tones_.push_back(new_tone); - return true; + tones_.push_back(new_tone); + return true; } -void ToneDetectorSet::process_sample(const float &sample) { - for (vector::iterator it = tones_.begin(); it != tones_.end(); ++it) { - it->process_sample(sample); - } +void ToneDetectorSet::process_sample(const float& sample) { + for (vector::iterator it = tones_.begin(); it != tones_.end(); ++it) { + it->process_sample(sample); + } } void ToneDetectorSet::reset(void) { - for (vector::iterator it = tones_.begin(); it != tones_.end(); ++it) { - it->reset(); - } + for (vector::iterator it = tones_.begin(); it != tones_.end(); ++it) { + it->reset(); + } } -float ToneDetectorSet::sorted_powers(vector &powers) { - powers.clear(); +float ToneDetectorSet::sorted_powers(vector& powers) { + powers.clear(); - float total_power = 0.0; - for (size_t i = 0; i < tones_.size(); ++i) { - powers.push_back({tones_[i].relative_power(), tones_[i].freq()}); - total_power += tones_[i].relative_power(); - } + float total_power = 0.0; + for (size_t i = 0; i < tones_.size(); ++i) { + powers.push_back({tones_[i].relative_power(), tones_[i].freq()}); + total_power += tones_[i].relative_power(); + } - sort(powers.begin(), powers.end(), [](PowerIndex a, PowerIndex b) { - return a.power > b.power; - }); - - return total_power / tones_.size(); -} + sort(powers.begin(), powers.end(), [](PowerIndex a, PowerIndex b) { return a.power > b.power; }); -vector CTCSS::standard_tones = { - 67.0, 69.3, 71.9, 74.4, 77.0, 79.7, 82.5, 85.4, 88.5, 91.5, 94.8, 97.4, 100.0, 103.5, 107.2, - 110.9, 114.8, 118.8, 123.0, 127.3, 131.8, 136.5, 141.3, 146.2, 150.0, 151.4, 156.7, 159.8, - 162.2, 165.5, 167.9, 171.3, 173.8, 177.3, 179.9, 183.5, 186.2, 189.9, 192.8, 196.6, 199.5, - 203.5, 206.5, 210.7, 218.1, 225.7, 229.1, 233.6, 241.8, 250.3, 254.1 -}; + return total_power / tones_.size(); +} -CTCSS::CTCSS(const float & ctcss_freq, const float & sample_rate, int window_size) - : enabled_(true), ctcss_freq_(ctcss_freq), window_size_(window_size), found_count_(0), not_found_count_(0) { +vector CTCSS::standard_tones = {67.0, 69.3, 71.9, 74.4, 77.0, 79.7, 82.5, 85.4, 88.5, 91.5, 94.8, 97.4, 100.0, 103.5, 107.2, 110.9, 114.8, + 118.8, 123.0, 127.3, 131.8, 136.5, 141.3, 146.2, 150.0, 151.4, 156.7, 159.8, 162.2, 165.5, 167.9, 171.3, 173.8, 177.3, + 179.9, 183.5, 186.2, 189.9, 192.8, 196.6, 199.5, 203.5, 206.5, 210.7, 218.1, 225.7, 229.1, 233.6, 241.8, 250.3, 254.1}; +CTCSS::CTCSS(const float& ctcss_freq, const float& sample_rate, int window_size) : enabled_(true), ctcss_freq_(ctcss_freq), window_size_(window_size), found_count_(0), not_found_count_(0) { debug_print("Adding CTCSS detector for %f Hz with a sample rate of %f and window %d\n", ctcss_freq, sample_rate, window_size_); - // Add the target CTCSS frequency first followed by the other "standard tones", except those - // within +/- 5 Hz - powers_.add(ctcss_freq, sample_rate, window_size_); - - for (const auto tone : standard_tones) { - if (abs(ctcss_freq - tone) < 5) { - debug_print("Skipping tone %f, too close to other tones\n", tone); - continue; - } - powers_.add(tone, sample_rate, window_size_); - } - + // Add the target CTCSS frequency first followed by the other "standard tones", except those + // within +/- 5 Hz + powers_.add(ctcss_freq, sample_rate, window_size_); + + for (const auto tone : standard_tones) { + if (abs(ctcss_freq - tone) < 5) { + debug_print("Skipping tone %f, too close to other tones\n", tone); + continue; + } + powers_.add(tone, sample_rate, window_size_); + } + // clear all values to start NOTE: has_tone_ will be true until the first window count of samples are processed reset(); } +void CTCSS::process_audio_sample(const float& sample) { + if (!enabled_) { + return; + } + + powers_.process_sample(sample); + + sample_count_++; + if (sample_count_ < window_size_) { + return; + } -void CTCSS::process_audio_sample(const float &sample) { - if (!enabled_) { - return; - } - - powers_.process_sample(sample); - - sample_count_++; - if (sample_count_ < window_size_) { - return; - } - enough_samples_ = true; - // if this is sample fills out the window then check if one of the "strongest" - // tones is the CTCSS tone we are looking for. NOTE: there can be multiple "strongest" - // tones based on floating point math - vector tone_powers; - float avg_power = powers_.sorted_powers(tone_powers); - float ctcss_tone_power = 0.0; - for( const auto i:tone_powers) { - if (i.freq == ctcss_freq_) { - ctcss_tone_power = i.power; - break; - } - } - if (ctcss_tone_power == tone_powers[0].power && ctcss_tone_power > avg_power) { + // if this is sample fills out the window then check if one of the "strongest" + // tones is the CTCSS tone we are looking for. NOTE: there can be multiple "strongest" + // tones based on floating point math + vector tone_powers; + float avg_power = powers_.sorted_powers(tone_powers); + float ctcss_tone_power = 0.0; + for (const auto i : tone_powers) { + if (i.freq == ctcss_freq_) { + ctcss_tone_power = i.power; + break; + } + } + if (ctcss_tone_power == tone_powers[0].power && ctcss_tone_power > avg_power) { debug_print("CTCSS tone of %f Hz detected\n", ctcss_freq_); has_tone_ = true; found_count_++; } else { - debug_print("CTCSS tone of %f Hz not detected - highest power was %f Hz at %f vs %f\n", - ctcss_freq_, tone_powers[0].freq, tone_powers[0].power, ctcss_tone_power); + debug_print("CTCSS tone of %f Hz not detected - highest power was %f Hz at %f vs %f\n", ctcss_freq_, tone_powers[0].freq, tone_powers[0].power, ctcss_tone_power); has_tone_ = false; not_found_count_++; } // reset everything for the next window's worth of samples - powers_.reset(); - sample_count_ = 0; + powers_.reset(); + sample_count_ = 0; } void CTCSS::reset(void) { - if (enabled_) { - powers_.reset(); + if (enabled_) { + powers_.reset(); enough_samples_ = false; - sample_count_ = 0; - has_tone_ = false; - } + sample_count_ = 0; + has_tone_ = false; + } } From 365c0e0758b65cf2874e0c8736844e4124562f7b Mon Sep 17 00:00:00 2001 From: charlie-foxtrot <13514783+charlie-foxtrot@users.noreply.github.com> Date: Tue, 6 Feb 2024 19:39:49 -0800 Subject: [PATCH 2/9] fix sources --- .github/workflows/code_formatting.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/code_formatting.yml b/.github/workflows/code_formatting.yml index 328c883..e5a447e 100644 --- a/.github/workflows/code_formatting.yml +++ b/.github/workflows/code_formatting.yml @@ -9,8 +9,8 @@ jobs: code_formatting: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: RafikFarhad/clang-format-github-action@v3 with: - sources: "src/**/*.h,src/**/*.cpp" + sources: "src/*.h,src/*.cpp" style: "file" From e89dc4367d60230f7fdfef0519c775303c4e65d5 Mon Sep 17 00:00:00 2001 From: charlie-foxtrot <13514783+charlie-foxtrot@users.noreply.github.com> Date: Tue, 6 Feb 2024 21:27:56 -0800 Subject: [PATCH 3/9] get files --- .github/workflows/code_formatting.yml | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/.github/workflows/code_formatting.yml b/.github/workflows/code_formatting.yml index e5a447e..bcd8d68 100644 --- a/.github/workflows/code_formatting.yml +++ b/.github/workflows/code_formatting.yml @@ -9,8 +9,18 @@ jobs: code_formatting: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: RafikFarhad/clang-format-github-action@v3 - with: - sources: "src/*.h,src/*.cpp" - style: "file" + + - name: Runner Info + run: printenv | sort + + - name: Checkout + uses: actions/checkout@v4 + + - name: working dir + run: find . + + - name: clang-format + uses: RafikFarhad/clang-format-github-action@v3 + with: + sources: "src/*.h,src/*.cpp" + style: "file" From 21091473b0aac3ea0ef8f8517eb77dcdd95585b8 Mon Sep 17 00:00:00 2001 From: charlie-foxtrot <13514783+charlie-foxtrot@users.noreply.github.com> Date: Tue, 6 Feb 2024 22:00:47 -0800 Subject: [PATCH 4/9] add script to reformat, change workflow to run script --- .devcontainer/Dockerfile.ubuntu-20.04 | 2 +- .devcontainer/Dockerfile.ubuntu-latest | 2 +- .github/workflows/code_formatting.yml | 13 ++++++------- scripts/reformat_code | 3 +++ 4 files changed, 11 insertions(+), 9 deletions(-) create mode 100755 scripts/reformat_code diff --git a/.devcontainer/Dockerfile.ubuntu-20.04 b/.devcontainer/Dockerfile.ubuntu-20.04 index 59d94a9..a0bcff7 100644 --- a/.devcontainer/Dockerfile.ubuntu-20.04 +++ b/.devcontainer/Dockerfile.ubuntu-20.04 @@ -11,7 +11,7 @@ RUN DEBIAN_FRONTEND=noninteractive \ git \ sudo \ gdb \ - clang-format + clang-format-14 WORKDIR /app COPY .github/install_dependencies /app/ diff --git a/.devcontainer/Dockerfile.ubuntu-latest b/.devcontainer/Dockerfile.ubuntu-latest index 3a60736..5515542 100644 --- a/.devcontainer/Dockerfile.ubuntu-latest +++ b/.devcontainer/Dockerfile.ubuntu-latest @@ -11,7 +11,7 @@ RUN DEBIAN_FRONTEND=noninteractive \ git \ sudo \ gdb \ - clang-format + clang-format-14 WORKDIR /app COPY .github/install_dependencies /app/ diff --git a/.github/workflows/code_formatting.yml b/.github/workflows/code_formatting.yml index bcd8d68..861e6e1 100644 --- a/.github/workflows/code_formatting.yml +++ b/.github/workflows/code_formatting.yml @@ -16,11 +16,10 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: working dir - run: find . + - name: Install Clang Format + run: sudo apt-get install clang-format-14 - - name: clang-format - uses: RafikFarhad/clang-format-github-action@v3 - with: - sources: "src/*.h,src/*.cpp" - style: "file" + - name: Run Clang Format + run: | + ./scripts/reformat_code + git diff --exit-code diff --git a/scripts/reformat_code b/scripts/reformat_code new file mode 100755 index 0000000..3687fd3 --- /dev/null +++ b/scripts/reformat_code @@ -0,0 +1,3 @@ +#!/bin/bash + +find src/*.h src/*.cpp | xargs clang-format-14 -i From 0da955f5a997bfe623730b28051a527fa5df8d33 Mon Sep 17 00:00:00 2001 From: charlie-foxtrot <13514783+charlie-foxtrot@users.noreply.github.com> Date: Tue, 6 Feb 2024 22:02:39 -0800 Subject: [PATCH 5/9] apply formatting --- src/config.cpp | 1651 ++++++++++++++-------------- src/ctcss.h | 129 ++- src/filters.cpp | 216 ++-- src/filters.h | 71 +- src/generate_signal.cpp | 70 +- src/generate_signal.h | 26 +- src/helper_functions.cpp | 30 +- src/helper_functions.h | 12 +- src/input-common.cpp | 186 ++-- src/input-common.h | 63 +- src/input-file.cpp | 287 +++-- src/input-file.h | 10 +- src/input-helpers.cpp | 63 +- src/input-helpers.h | 4 +- src/input-mirisdr.cpp | 388 ++++--- src/input-mirisdr.h | 14 +- src/input-rtlsdr.cpp | 417 ++++--- src/input-rtlsdr.h | 15 +- src/input-soapysdr.cpp | 639 ++++++----- src/input-soapysdr.h | 22 +- src/logging.cpp | 65 +- src/logging.h | 30 +- src/mixer.cpp | 362 +++---- src/output.cpp | 1687 ++++++++++++++--------------- src/pulse.cpp | 385 ++++--- src/rtl_airband.cpp | 1912 ++++++++++++++++----------------- src/rtl_airband.h | 428 ++++---- src/squelch.cpp | 945 ++++++++-------- src/squelch.h | 196 ++-- src/test_base_class.cpp | 185 ++-- src/test_base_class.h | 11 +- src/test_ctcss.cpp | 243 ++--- src/test_filters.cpp | 29 +- src/test_generate_signal.cpp | 444 ++++---- src/test_helper_functions.cpp | 204 ++-- src/test_squelch.cpp | 468 ++++---- src/udp_stream.cpp | 144 +-- src/util.cpp | 230 ++-- 38 files changed, 6037 insertions(+), 6244 deletions(-) diff --git a/src/config.cpp b/src/config.cpp index 7e451c1..d4f042a 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -18,894 +18,869 @@ * along with this program. If not, see . */ -#include -#include -#include -#include #include -#include // uint32_t +#include // uint32_t #include +#include +#include +#include +#include #include -#include "input-common.h" // input_t +#include "input-common.h" // input_t #include "rtl_airband.h" using namespace std; -static int parse_outputs(libconfig::Setting &outs, channel_t *channel, int i, int j, bool parsing_mixers) { - int oo = 0; - for(int o = 0; o < channel->output_count; o++) { - if(outs[o].exists("disable") && (bool)outs[o]["disable"] == true) { - continue; - } - if(!strncmp(outs[o]["type"], "icecast", 7)) { - channel->outputs[oo].data = XCALLOC(1, sizeof(struct icecast_data)); - channel->outputs[oo].type = O_ICECAST; - icecast_data *idata = (icecast_data *)(channel->outputs[oo].data); - idata->hostname = strdup(outs[o]["server"]); - idata->port = outs[o]["port"]; - idata->mountpoint = strdup(outs[o]["mountpoint"]); - idata->username = strdup(outs[o]["username"]); - idata->password = strdup(outs[o]["password"]); - if(outs[o].exists("name")) - idata->name = strdup(outs[o]["name"]); - if(outs[o].exists("genre")) - idata->genre = strdup(outs[o]["genre"]); - if(outs[o].exists("description")) - idata->description = strdup(outs[o]["description"]); - if(outs[o].exists("send_scan_freq_tags")) - idata->send_scan_freq_tags = (bool)outs[o]["send_scan_freq_tags"]; - else - idata->send_scan_freq_tags = 0; +static int parse_outputs(libconfig::Setting& outs, channel_t* channel, int i, int j, bool parsing_mixers) { + int oo = 0; + for (int o = 0; o < channel->output_count; o++) { + if (outs[o].exists("disable") && (bool)outs[o]["disable"] == true) { + continue; + } + if (!strncmp(outs[o]["type"], "icecast", 7)) { + channel->outputs[oo].data = XCALLOC(1, sizeof(struct icecast_data)); + channel->outputs[oo].type = O_ICECAST; + icecast_data* idata = (icecast_data*)(channel->outputs[oo].data); + idata->hostname = strdup(outs[o]["server"]); + idata->port = outs[o]["port"]; + idata->mountpoint = strdup(outs[o]["mountpoint"]); + idata->username = strdup(outs[o]["username"]); + idata->password = strdup(outs[o]["password"]); + if (outs[o].exists("name")) + idata->name = strdup(outs[o]["name"]); + if (outs[o].exists("genre")) + idata->genre = strdup(outs[o]["genre"]); + if (outs[o].exists("description")) + idata->description = strdup(outs[o]["description"]); + if (outs[o].exists("send_scan_freq_tags")) + idata->send_scan_freq_tags = (bool)outs[o]["send_scan_freq_tags"]; + else + idata->send_scan_freq_tags = 0; #ifdef LIBSHOUT_HAS_TLS - if(outs[o].exists("tls")) { - if(outs[o]["tls"].getType() == libconfig::Setting::TypeString) { - if(!strcmp(outs[o]["tls"], "auto")) { - idata->tls_mode = SHOUT_TLS_AUTO; - } else if(!strcmp(outs[o]["tls"], "auto_no_plain")) { - idata->tls_mode = SHOUT_TLS_AUTO_NO_PLAIN; - } else if(!strcmp(outs[o]["tls"], "transport")) { - idata->tls_mode = SHOUT_TLS_RFC2818; - } else if(!strcmp(outs[o]["tls"], "upgrade")) { - idata->tls_mode = SHOUT_TLS_RFC2817; - } else if(!strcmp(outs[o]["tls"], "disabled")) { - idata->tls_mode = SHOUT_TLS_DISABLED; - } else { - if (parsing_mixers) { - cerr<<"Configuration error: mixers.["<tls_mode = SHOUT_TLS_DISABLED; - } + if (outs[o].exists("tls")) { + if (outs[o]["tls"].getType() == libconfig::Setting::TypeString) { + if (!strcmp(outs[o]["tls"], "auto")) { + idata->tls_mode = SHOUT_TLS_AUTO; + } else if (!strcmp(outs[o]["tls"], "auto_no_plain")) { + idata->tls_mode = SHOUT_TLS_AUTO_NO_PLAIN; + } else if (!strcmp(outs[o]["tls"], "transport")) { + idata->tls_mode = SHOUT_TLS_RFC2818; + } else if (!strcmp(outs[o]["tls"], "upgrade")) { + idata->tls_mode = SHOUT_TLS_RFC2817; + } else if (!strcmp(outs[o]["tls"], "disabled")) { + idata->tls_mode = SHOUT_TLS_DISABLED; + } else { + if (parsing_mixers) { + cerr << "Configuration error: mixers.[" << i << "] outputs.[" << o << "]: "; + } else { + cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "] outputs.[" << o << "]: "; + } + cerr << "invalid value for tls; must be one of: auto, auto_no_plain, transport, upgrade, disabled\n"; + error(); + } + } else { + if (parsing_mixers) { + cerr << "Configuration error: mixers.[" << i << "] outputs.[" << o << "]: "; + } else { + cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "] outputs.[" << o << "]: "; + } + cerr << "tls value must be a string\n"; + error(); + } + } else { + idata->tls_mode = SHOUT_TLS_DISABLED; + } #endif /* LIBSHOUT_HAS_TLS */ - channel->need_mp3 = 1; - } else if(!strncmp(outs[o]["type"], "file", 4)) { - channel->outputs[oo].data = XCALLOC(1, sizeof(struct file_data)); - channel->outputs[oo].type = O_FILE; - file_data *fdata = (file_data *)(channel->outputs[oo].data); - - fdata->type = O_FILE; - if (!outs[o].exists("directory") || !outs[o].exists("filename_template")) { - if(parsing_mixers) { - cerr << "Configuration error: mixers.["<basedir = outs[o]["directory"].c_str(); - fdata->basename = outs[o]["filename_template"].c_str(); - fdata->dated_subdirectories = outs[o].exists("dated_subdirectories") ? - (bool)(outs[o]["dated_subdirectories"]) : false; - fdata->suffix = ".mp3"; - - fdata->continuous = outs[o].exists("continuous") ? - (bool)(outs[o]["continuous"]) : false; - fdata->append = (!outs[o].exists("append")) || (bool)(outs[o]["append"]); - fdata->split_on_transmission = outs[o].exists("split_on_transmission") ? - (bool)(outs[o]["split_on_transmission"]) : false; - fdata->include_freq = outs[o].exists("include_freq") ? - (bool)(outs[o]["include_freq"]) : false; - channel->need_mp3 = 1; - - if(fdata->split_on_transmission) { - if (parsing_mixers) { - cerr<<"Configuration error: mixers.["<continuous) { - cerr<<"Configuration error: devices.["<outputs[oo].data = XCALLOC(1, sizeof(struct file_data)); - channel->outputs[oo].type = O_RAWFILE; - file_data *fdata = (file_data *)(channel->outputs[oo].data); - - fdata->type = O_RAWFILE; - if (!outs[o].exists("directory") || !outs[o].exists("filename_template")) { - cerr<<"Configuration error: devices.["<basedir = outs[o]["directory"].c_str(); - fdata->basename = outs[o]["filename_template"].c_str(); - fdata->dated_subdirectories = outs[o].exists("dated_subdirectories") ? - (bool)(outs[o]["dated_subdirectories"]) : false; - fdata->suffix = ".cf32"; - - fdata->continuous = outs[o].exists("continuous") ? - (bool)(outs[o]["continuous"]) : false; - fdata->append = (!outs[o].exists("append")) || (bool)(outs[o]["append"]); - fdata->split_on_transmission = outs[o].exists("split_on_transmission") ? - (bool)(outs[o]["split_on_transmission"]) : false; - fdata->include_freq = outs[o].exists("include_freq") ? - (bool)(outs[o]["include_freq"]) : false; - channel->needs_raw_iq = channel->has_iq_outputs = 1; - - if(fdata->continuous && fdata->split_on_transmission) { - cerr<<"Configuration error: devices.["<outputs[oo].data = XCALLOC(1, sizeof(struct mixer_data)); - channel->outputs[oo].type = O_MIXER; - mixer_data *mdata = (mixer_data *)(channel->outputs[oo].data); - const char *name = (const char *)outs[o]["name"]; - if((mdata->mixer = getmixerbyname(name)) == NULL) { - cerr<<"Configuration error: devices.["< 1.0f) { - cerr<<"Configuration error: devices.["<\n"; - error(); - } - if((mdata->input = mixer_connect_input(mdata->mixer, ampfactor, balance)) < 0) { - cerr<<"Configuration error: devices.["<input, ampfactor, balance); - } else if(!strncmp(outs[o]["type"], "udp_stream", 6)) { - channel->outputs[oo].data = XCALLOC(1, sizeof(struct udp_stream_data)); - channel->outputs[oo].type = O_UDP_STREAM; - - udp_stream_data *sdata = (udp_stream_data *)channel->outputs[oo].data; - - sdata->continuous = outs[o].exists("continuous") ? (bool)(outs[o]["continuous"]) : false; - - if (outs[o].exists("dest_address")) { - sdata->dest_address = strdup(outs[o]["dest_address"]); - } else { - if (parsing_mixers) { - cerr << "Configuration error: mixers.["<dest_port = strdup(buffer); - } else { - sdata->dest_port = strdup(outs[o]["dest_port"]); - } - } else { - if (parsing_mixers) { - cerr << "Configuration error: mixers.["<need_mp3 = 1; + } else if (!strncmp(outs[o]["type"], "file", 4)) { + channel->outputs[oo].data = XCALLOC(1, sizeof(struct file_data)); + channel->outputs[oo].type = O_FILE; + file_data* fdata = (file_data*)(channel->outputs[oo].data); + + fdata->type = O_FILE; + if (!outs[o].exists("directory") || !outs[o].exists("filename_template")) { + if (parsing_mixers) { + cerr << "Configuration error: mixers.[" << i << "] outputs.[" << o << "]: "; + } else { + cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "] outputs.[" << o << "]: "; + } + cerr << "both directory and filename_template required for file\n"; + error(); + } + fdata->basedir = outs[o]["directory"].c_str(); + fdata->basename = outs[o]["filename_template"].c_str(); + fdata->dated_subdirectories = outs[o].exists("dated_subdirectories") ? (bool)(outs[o]["dated_subdirectories"]) : false; + fdata->suffix = ".mp3"; + + fdata->continuous = outs[o].exists("continuous") ? (bool)(outs[o]["continuous"]) : false; + fdata->append = (!outs[o].exists("append")) || (bool)(outs[o]["append"]); + fdata->split_on_transmission = outs[o].exists("split_on_transmission") ? (bool)(outs[o]["split_on_transmission"]) : false; + fdata->include_freq = outs[o].exists("include_freq") ? (bool)(outs[o]["include_freq"]) : false; + channel->need_mp3 = 1; + + if (fdata->split_on_transmission) { + if (parsing_mixers) { + cerr << "Configuration error: mixers.[" << i << "] outputs.[" << o << "]: split_on_transmission is not allowed for mixers\n"; + error(); + } + if (fdata->continuous) { + cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "] outputs.[" << o << "]: can't have both continuous and split_on_transmission\n"; + error(); + } + } + + } else if (!strncmp(outs[o]["type"], "rawfile", 7)) { + if (parsing_mixers) { // rawfile outputs not allowed for mixers + cerr << "Configuration error: mixers.[" << i << "] outputs[" << o << "]: rawfile output is not allowed for mixers\n"; + error(); + } + channel->outputs[oo].data = XCALLOC(1, sizeof(struct file_data)); + channel->outputs[oo].type = O_RAWFILE; + file_data* fdata = (file_data*)(channel->outputs[oo].data); + + fdata->type = O_RAWFILE; + if (!outs[o].exists("directory") || !outs[o].exists("filename_template")) { + cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "] outputs.[" << o << "]: both directory and filename_template required for file\n"; + error(); + } + + fdata->basedir = outs[o]["directory"].c_str(); + fdata->basename = outs[o]["filename_template"].c_str(); + fdata->dated_subdirectories = outs[o].exists("dated_subdirectories") ? (bool)(outs[o]["dated_subdirectories"]) : false; + fdata->suffix = ".cf32"; + + fdata->continuous = outs[o].exists("continuous") ? (bool)(outs[o]["continuous"]) : false; + fdata->append = (!outs[o].exists("append")) || (bool)(outs[o]["append"]); + fdata->split_on_transmission = outs[o].exists("split_on_transmission") ? (bool)(outs[o]["split_on_transmission"]) : false; + fdata->include_freq = outs[o].exists("include_freq") ? (bool)(outs[o]["include_freq"]) : false; + channel->needs_raw_iq = channel->has_iq_outputs = 1; + + if (fdata->continuous && fdata->split_on_transmission) { + cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "] outputs.[" << o << "]: can't have both continuous and split_on_transmission\n"; + error(); + } + } else if (!strncmp(outs[o]["type"], "mixer", 5)) { + if (parsing_mixers) { // mixer outputs not allowed for mixers + cerr << "Configuration error: mixers.[" << i << "] outputs.[" << o << "]: mixer output is not allowed for mixers\n"; + error(); + } + channel->outputs[oo].data = XCALLOC(1, sizeof(struct mixer_data)); + channel->outputs[oo].type = O_MIXER; + mixer_data* mdata = (mixer_data*)(channel->outputs[oo].data); + const char* name = (const char*)outs[o]["name"]; + if ((mdata->mixer = getmixerbyname(name)) == NULL) { + cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "] outputs.[" << o << "]: unknown mixer \"" << name << "\"\n"; + error(); + } + float ampfactor = outs[o].exists("ampfactor") ? (float)outs[o]["ampfactor"] : 1.0f; + float balance = outs[o].exists("balance") ? (float)outs[o]["balance"] : 0.0f; + if (balance < -1.0f || balance > 1.0f) { + cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "] outputs.[" << o << "]: balance out of allowed range <-1.0;1.0>\n"; + error(); + } + if ((mdata->input = mixer_connect_input(mdata->mixer, ampfactor, balance)) < 0) { + cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "] outputs.[" << o + << "]: " + "could not connect to mixer " + << name << ": " << mixer_get_error() << "\n"; + error(); + } + debug_print("dev[%d].chan[%d].out[%d] connected to mixer %s as input %d (ampfactor=%.1f balance=%.1f)\n", i, j, o, name, mdata->input, ampfactor, balance); + } else if (!strncmp(outs[o]["type"], "udp_stream", 6)) { + channel->outputs[oo].data = XCALLOC(1, sizeof(struct udp_stream_data)); + channel->outputs[oo].type = O_UDP_STREAM; + + udp_stream_data* sdata = (udp_stream_data*)channel->outputs[oo].data; + + sdata->continuous = outs[o].exists("continuous") ? (bool)(outs[o]["continuous"]) : false; + + if (outs[o].exists("dest_address")) { + sdata->dest_address = strdup(outs[o]["dest_address"]); + } else { + if (parsing_mixers) { + cerr << "Configuration error: mixers.[" << i << "] outputs.[" << o << "]: "; + } else { + cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "] outputs.[" << o << "]: "; + } + cerr << "missing dest_address\n"; + error(); + } + + if (outs[o].exists("dest_port")) { + if (outs[o]["dest_port"].getType() == libconfig::Setting::TypeInt) { + char buffer[12]; + sprintf(buffer, "%d", (int)outs[o]["dest_port"]); + sdata->dest_port = strdup(buffer); + } else { + sdata->dest_port = strdup(outs[o]["dest_port"]); + } + } else { + if (parsing_mixers) { + cerr << "Configuration error: mixers.[" << i << "] outputs.[" << o << "]: "; + } else { + cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "] outputs.[" << o << "]: "; + } + cerr << "missing dest_port\n"; + error(); + } #ifdef WITH_PULSEAUDIO - } else if(!strncmp(outs[o]["type"], "pulse", 5)) { - channel->outputs[oo].data = XCALLOC(1, sizeof(struct pulse_data)); - channel->outputs[oo].type = O_PULSE; - - pulse_data *pdata = (pulse_data *)(channel->outputs[oo].data); - pdata->continuous = outs[o].exists("continuous") ? - (bool)(outs[o]["continuous"]) : false; - pdata->server = outs[o].exists("server") ? strdup(outs[o]["server"]) : NULL; - pdata->name = outs[o].exists("name") ? strdup(outs[o]["name"]) : "rtl_airband"; - pdata->sink = outs[o].exists("sink") ? strdup(outs[o]["sink"]) : NULL; - - if (outs[o].exists("stream_name")) { - pdata->stream_name = strdup(outs[o]["stream_name"]); - } else { - if(parsing_mixers) { - cerr<<"Configuration error: mixers.["<freqlist[0].frequency / 1000000.0f); - pdata->stream_name = strdup(buf); - } + } else if (!strncmp(outs[o]["type"], "pulse", 5)) { + channel->outputs[oo].data = XCALLOC(1, sizeof(struct pulse_data)); + channel->outputs[oo].type = O_PULSE; + + pulse_data* pdata = (pulse_data*)(channel->outputs[oo].data); + pdata->continuous = outs[o].exists("continuous") ? (bool)(outs[o]["continuous"]) : false; + pdata->server = outs[o].exists("server") ? strdup(outs[o]["server"]) : NULL; + pdata->name = outs[o].exists("name") ? strdup(outs[o]["name"]) : "rtl_airband"; + pdata->sink = outs[o].exists("sink") ? strdup(outs[o]["sink"]) : NULL; + + if (outs[o].exists("stream_name")) { + pdata->stream_name = strdup(outs[o]["stream_name"]); + } else { + if (parsing_mixers) { + cerr << "Configuration error: mixers.[" << i << "] outputs.[" << o << "]: PulseAudio outputs of mixers must have stream_name defined\n"; + error(); + } + char buf[1024]; + snprintf(buf, sizeof(buf), "%.3f MHz", (float)channel->freqlist[0].frequency / 1000000.0f); + pdata->stream_name = strdup(buf); + } #endif /* WITH_PULSEAUDIO */ - } else { - if(parsing_mixers) { - cerr << "Configuration error: mixers.["<outputs[oo].enabled = true; - channel->outputs[oo].active = false; - oo++; - } - return oo; + } else { + if (parsing_mixers) { + cerr << "Configuration error: mixers.[" << i << "] outputs.[" << o << "]: "; + } else { + cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "] outputs.[" << o << "]: "; + } + cerr << "unknown output type\n"; + error(); + } + channel->outputs[oo].enabled = true; + channel->outputs[oo].active = false; + oo++; + } + return oo; } -static struct freq_t *mk_freqlist( int n ) -{ - if(n < 1) { - cerr<<"mk_freqlist: invalid list length " << n << "\n"; - error(); - } - struct freq_t *fl = (struct freq_t *)XCALLOC(n, sizeof(struct freq_t)); - for(int i = 0; i < n; i++) { - fl[i].frequency = 0; - fl[i].label = NULL; - fl[i].agcavgfast = 0.5f; - fl[i].ampfactor = 1.0f; - fl[i].squelch = Squelch(); - fl[i].active_counter = 0; - fl[i].modulation = MOD_AM; - } - return fl; +static struct freq_t* mk_freqlist(int n) { + if (n < 1) { + cerr << "mk_freqlist: invalid list length " << n << "\n"; + error(); + } + struct freq_t* fl = (struct freq_t*)XCALLOC(n, sizeof(struct freq_t)); + for (int i = 0; i < n; i++) { + fl[i].frequency = 0; + fl[i].label = NULL; + fl[i].agcavgfast = 0.5f; + fl[i].ampfactor = 1.0f; + fl[i].squelch = Squelch(); + fl[i].active_counter = 0; + fl[i].modulation = MOD_AM; + } + return fl; } static void warn_if_freq_not_in_range(int devidx, int chanidx, int freq, int centerfreq, int sample_rate) { - static const float soft_bw_threshold = 0.9f; - float bw_limit = (float)sample_rate / 2.f * soft_bw_threshold; - if((float)abs(freq - centerfreq) >= bw_limit) { - log(LOG_WARNING, - "Warning: dev[%d].channel[%d]: frequency %.3f MHz is outside of SDR operating bandwidth (%.3f-%.3f MHz)\n", - devidx, chanidx, (double)freq / 1e6, - (double)(centerfreq - bw_limit) / 1e6, - (double)(centerfreq + bw_limit) / 1e6); - } + static const float soft_bw_threshold = 0.9f; + float bw_limit = (float)sample_rate / 2.f * soft_bw_threshold; + if ((float)abs(freq - centerfreq) >= bw_limit) { + log(LOG_WARNING, "Warning: dev[%d].channel[%d]: frequency %.3f MHz is outside of SDR operating bandwidth (%.3f-%.3f MHz)\n", devidx, chanidx, (double)freq / 1e6, + (double)(centerfreq - bw_limit) / 1e6, (double)(centerfreq + bw_limit) / 1e6); + } } static int parse_anynum2int(libconfig::Setting& f) { - int ret = 0; - if(f.getType() == libconfig::Setting::TypeInt) { - ret = (int)f; - } else if(f.getType() == libconfig::Setting::TypeFloat) { - ret = (int)((double)f * 1e6); - } else if(f.getType() == libconfig::Setting::TypeString) { - char *s = strdup((char const *)f); - ret = (int)atofs(s); - free(s); - } - return ret; + int ret = 0; + if (f.getType() == libconfig::Setting::TypeInt) { + ret = (int)f; + } else if (f.getType() == libconfig::Setting::TypeFloat) { + ret = (int)((double)f * 1e6); + } else if (f.getType() == libconfig::Setting::TypeString) { + char* s = strdup((char const*)f); + ret = (int)atofs(s); + free(s); + } + return ret; } -static int parse_channels(libconfig::Setting &chans, device_t *dev, int i) { - int jj = 0; - for (int j = 0; j < chans.getLength(); j++) { - if(chans[j].exists("disable") && (bool)chans[j]["disable"] == true) { - continue; - } - channel_t* channel = dev->channels + jj; - for (int k = 0; k < AGC_EXTRA; k++) { - channel->wavein[k] = 20; - channel->waveout[k] = 0.5; - } - channel->axcindicate = NO_SIGNAL; - channel->mode = MM_MONO; - channel->need_mp3 = 0; - channel->freq_count = 1; - channel->freq_idx = 0; - channel->highpass = chans[j].exists("highpass") ? (int)chans[j]["highpass"] : 100; - channel->lowpass = chans[j].exists("lowpass") ? (int)chans[j]["lowpass"] : 2500; - channel->lame = NULL; - channel->lamebuf = NULL; +static int parse_channels(libconfig::Setting& chans, device_t* dev, int i) { + int jj = 0; + for (int j = 0; j < chans.getLength(); j++) { + if (chans[j].exists("disable") && (bool)chans[j]["disable"] == true) { + continue; + } + channel_t* channel = dev->channels + jj; + for (int k = 0; k < AGC_EXTRA; k++) { + channel->wavein[k] = 20; + channel->waveout[k] = 0.5; + } + channel->axcindicate = NO_SIGNAL; + channel->mode = MM_MONO; + channel->need_mp3 = 0; + channel->freq_count = 1; + channel->freq_idx = 0; + channel->highpass = chans[j].exists("highpass") ? (int)chans[j]["highpass"] : 100; + channel->lowpass = chans[j].exists("lowpass") ? (int)chans[j]["lowpass"] : 2500; + channel->lame = NULL; + channel->lamebuf = NULL; #ifdef NFM - channel->pr = 0; - channel->pj = 0; - channel->prev_waveout = 0.5; - channel->alpha = dev->alpha; + channel->pr = 0; + channel->pj = 0; + channel->prev_waveout = 0.5; + channel->alpha = dev->alpha; #endif /* NFM */ - // Make sure lowpass / highpass aren't flipped. - // If lowpass is enabled (greater than zero) it must be larger than highpass - if (channel->lowpass > 0 && channel->lowpass < channel->highpass) { - cerr << "Configuration error: devices.["<lowpass << ") must be greater than or equal to highpass (" << channel->highpass << ")\n"; - error(); - } + // Make sure lowpass / highpass aren't flipped. + // If lowpass is enabled (greater than zero) it must be larger than highpass + if (channel->lowpass > 0 && channel->lowpass < channel->highpass) { + cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "]: lowpass (" << channel->lowpass << ") must be greater than or equal to highpass (" << channel->highpass << ")\n"; + error(); + } - modulations channel_modulation = MOD_AM; - if(chans[j].exists("modulation")) { + modulations channel_modulation = MOD_AM; + if (chans[j].exists("modulation")) { #ifdef NFM - if(strncmp(chans[j]["modulation"], "nfm", 3) == 0) { - channel_modulation = MOD_NFM; - } else + if (strncmp(chans[j]["modulation"], "nfm", 3) == 0) { + channel_modulation = MOD_NFM; + } else #endif /* NFM */ - if(strncmp(chans[j]["modulation"], "am", 2) != 0) { - cerr<<"Configuration error: devices.["<afc = chans[j].exists("afc") ? (unsigned char) (unsigned int)chans[j]["afc"] : 0; - if(dev->mode == R_MULTICHANNEL) { - channel->freqlist = mk_freqlist( 1 ); - channel->freqlist[0].frequency = parse_anynum2int(chans[j]["freq"]); - warn_if_freq_not_in_range(i, j, channel->freqlist[0].frequency, dev->input->centerfreq, dev->input->sample_rate); - if (chans[j].exists("label")) - { - channel->freqlist[0].label = strdup(chans[j]["label"]); - } - channel->freqlist[0].modulation = channel_modulation; - } else { /* R_SCAN */ - channel->freq_count = chans[j]["freqs"].getLength(); - if(channel->freq_count < 1) { - cerr<<"Configuration error: devices.["<freqlist = mk_freqlist( channel->freq_count ); - if(chans[j].exists("labels") && chans[j]["labels"].getLength() < channel->freq_count) { - cerr<<"Configuration error: devices.["<freq_count<<" elements\n"; - error(); - } - if(chans[j].exists("squelch_threshold") && libconfig::Setting::TypeList == chans[j]["squelch_threshold"].getType() && chans[j]["squelch_threshold"].getLength() < channel->freq_count) { - cerr<<"Configuration error: devices.["<freq_count<<" elements\n"; - error(); - } - if(chans[j].exists("squelch_snr_threshold") && libconfig::Setting::TypeList == chans[j]["squelch_snr_threshold"].getType() && chans[j]["squelch_snr_threshold"].getLength() < channel->freq_count) { - cerr<<"Configuration error: devices.["<freq_count<<" elements\n"; - error(); - } - if(chans[j].exists("notch") && libconfig::Setting::TypeList == chans[j]["notch"].getType() && chans[j]["notch"].getLength() < channel->freq_count) { - cerr<<"Configuration error: devices.["<freq_count<<" elements\n"; - error(); - } - if(chans[j].exists("notch_q") && libconfig::Setting::TypeList == chans[j]["notch_q"].getType() && chans[j]["notch_q"].getLength() < channel->freq_count) { - cerr<<"Configuration error: devices.["<freq_count<<" elements\n"; - error(); - } - if(chans[j].exists("ctcss") && libconfig::Setting::TypeList == chans[j]["ctcss"].getType() && chans[j]["ctcss"].getLength() < channel->freq_count) { - cerr<<"Configuration error: devices.["<freq_count<<" elements\n"; - error(); - } - if(chans[j].exists("modulation") && chans[j].exists("modulations")) { - cerr<<"Configuration error: devices.["<freq_count) { - cerr<<"Configuration error: devices.["<freq_count<<" elements\n"; - error(); - } - - for(int f = 0; ffreq_count; f++) { - channel->freqlist[f].frequency = parse_anynum2int((chans[j]["freqs"][f])); - if(chans[j].exists("labels")) { - channel->freqlist[f].label = strdup(chans[j]["labels"][f]); - } - if(chans[j].exists("modulations")) { + if (strncmp(chans[j]["modulation"], "am", 2) != 0) { + cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "]: unknown modulation\n"; + error(); + } + } + channel->afc = chans[j].exists("afc") ? (unsigned char)(unsigned int)chans[j]["afc"] : 0; + if (dev->mode == R_MULTICHANNEL) { + channel->freqlist = mk_freqlist(1); + channel->freqlist[0].frequency = parse_anynum2int(chans[j]["freq"]); + warn_if_freq_not_in_range(i, j, channel->freqlist[0].frequency, dev->input->centerfreq, dev->input->sample_rate); + if (chans[j].exists("label")) { + channel->freqlist[0].label = strdup(chans[j]["label"]); + } + channel->freqlist[0].modulation = channel_modulation; + } else { /* R_SCAN */ + channel->freq_count = chans[j]["freqs"].getLength(); + if (channel->freq_count < 1) { + cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "]: freqs should be a list with at least one element\n"; + error(); + } + channel->freqlist = mk_freqlist(channel->freq_count); + if (chans[j].exists("labels") && chans[j]["labels"].getLength() < channel->freq_count) { + cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "]: labels should be a list with at least " << channel->freq_count << " elements\n"; + error(); + } + if (chans[j].exists("squelch_threshold") && libconfig::Setting::TypeList == chans[j]["squelch_threshold"].getType() && chans[j]["squelch_threshold"].getLength() < channel->freq_count) { + cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "]: squelch_threshold should be an int or a list of ints with at least " << channel->freq_count + << " elements\n"; + error(); + } + if (chans[j].exists("squelch_snr_threshold") && libconfig::Setting::TypeList == chans[j]["squelch_snr_threshold"].getType() && + chans[j]["squelch_snr_threshold"].getLength() < channel->freq_count) { + cerr << "Configuration error: devices.[" << i << "] channels.[" << j + << "]: squelch_snr_threshold should be an int, a float or a list of " + "ints or floats with at least " + << channel->freq_count << " elements\n"; + error(); + } + if (chans[j].exists("notch") && libconfig::Setting::TypeList == chans[j]["notch"].getType() && chans[j]["notch"].getLength() < channel->freq_count) { + cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "]: notch should be an float or a list of floats with at least " << channel->freq_count << " elements\n"; + error(); + } + if (chans[j].exists("notch_q") && libconfig::Setting::TypeList == chans[j]["notch_q"].getType() && chans[j]["notch_q"].getLength() < channel->freq_count) { + cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "]: notch_q should be a float or a list of floats with at least " << channel->freq_count << " elements\n"; + error(); + } + if (chans[j].exists("ctcss") && libconfig::Setting::TypeList == chans[j]["ctcss"].getType() && chans[j]["ctcss"].getLength() < channel->freq_count) { + cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "]: ctcss should be an float or a list of floats with at least " << channel->freq_count << " elements\n"; + error(); + } + if (chans[j].exists("modulation") && chans[j].exists("modulations")) { + cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "]: can't set both modulation and modulations\n"; + error(); + } + if (chans[j].exists("modulations") && chans[j]["modulations"].getLength() < channel->freq_count) { + cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "]: modulations should be a list with at least " << channel->freq_count << " elements\n"; + error(); + } + + for (int f = 0; f < channel->freq_count; f++) { + channel->freqlist[f].frequency = parse_anynum2int((chans[j]["freqs"][f])); + if (chans[j].exists("labels")) { + channel->freqlist[f].label = strdup(chans[j]["labels"][f]); + } + if (chans[j].exists("modulations")) { #ifdef NFM - if(strncmp(chans[j]["modulations"][f], "nfm", 3) == 0) { - channel->freqlist[f].modulation = MOD_NFM; - } else + if (strncmp(chans[j]["modulations"][f], "nfm", 3) == 0) { + channel->freqlist[f].modulation = MOD_NFM; + } else #endif /* NFM */ - if(strncmp(chans[j]["modulations"][f], "am", 2) == 0) { - channel->freqlist[f].modulation = MOD_AM; - } else { - cerr<<"Configuration error: devices.["<freqlist[f].modulation = channel_modulation; - } - } -// Set initial frequency for scanning -// We tune 20 FFT bins higher to avoid DC spike - dev->input->centerfreq = channel->freqlist[0].frequency + 20 * (double)(dev->input->sample_rate / fft_size); - } - if(chans[j].exists("squelch")) { - cerr << "Warning: 'squelch' no longer supported and will be ignored, use 'squelch_threshold' or 'squelch_snr_threshold' instead\n"; - } - if(chans[j].exists("squelch_threshold") && chans[j].exists("squelch_snr_threshold")) { - cerr << "Warning: Both 'squelch_threshold' and 'squelch_snr_threshold' are set and may conflict\n"; - } - if(chans[j].exists("squelch_threshold")) { - // Value is dBFS, zero disables manual threshold (ie use auto squelch), negative is valid, positive is invalid - if(libconfig::Setting::TypeList == chans[j]["squelch_threshold"].getType()) { - // New-style array of per-frequency squelch settings - for(int f = 0; ffreq_count; f++) { - int threshold_dBFS = (int)chans[j]["squelch_threshold"][f]; - if (threshold_dBFS > 0) { - cerr << "Configuration error: devices.["<freqlist[f].squelch.set_squelch_level_threshold(0); - } else { - channel->freqlist[f].squelch.set_squelch_level_threshold(dBFS_to_level(threshold_dBFS)); - } - } - } else if(libconfig::Setting::TypeInt == chans[j]["squelch_threshold"].getType()) { - // Legacy (single squelch for all frequencies) - int threshold_dBFS = (int)chans[j]["squelch_threshold"]; - float level; - if (threshold_dBFS > 0) { - cerr << "Configuration error: devices.["<freq_count; f++) { - channel->freqlist[f].squelch.set_squelch_level_threshold(level); - } - } else { - cerr << "Invalid value for squelch_threshold (should be int or list - use parentheses)\n"; - error(); - } - } - if(chans[j].exists("squelch_snr_threshold")) { - // Value is SNR in dB, zero disables squelch (ie always open), -1 uses default value, positive is valid, other negative values are invalid - if(libconfig::Setting::TypeList == chans[j]["squelch_snr_threshold"].getType()) { - // New-style array of per-frequency squelch settings - for(int f = 0; ffreq_count; f++) { - float snr = 0.f; - if (libconfig::Setting::TypeFloat == chans[j]["squelch_snr_threshold"][f].getType()) { - snr = (float)chans[j]["squelch_snr_threshold"][f]; - } else if (libconfig::Setting::TypeInt == chans[j]["squelch_snr_threshold"][f].getType()) { - snr = (int)chans[j]["squelch_snr_threshold"][f]; - } else { - cerr << "Configuration error: devices.["<freqlist[f].squelch.set_squelch_snr_threshold(snr); - } - } - } else if(libconfig::Setting::TypeFloat == chans[j]["squelch_snr_threshold"].getType() || libconfig::Setting::TypeInt == chans[j]["squelch_snr_threshold"].getType()) { - // Legacy (single squelch for all frequencies) - float snr = (libconfig::Setting::TypeFloat == chans[j]["squelch_snr_threshold"].getType()) ? (float)chans[j]["squelch_snr_threshold"] : (int)chans[j]["squelch_snr_threshold"]; - - if (snr == -1.0) { - continue; // "disable" so use the default without error message - } else if (snr < 0) { - cerr << "Configuration error: devices.["<freq_count; f++) { - channel->freqlist[f].squelch.set_squelch_snr_threshold(snr); - } - } else { - cerr << "Invalid value for squelch_snr_threshold (should be float, int, or list of int/float - use parentheses)\n"; - error(); - } - } - if(chans[j].exists("notch")) { - static const float default_q = 10.0; - - if(chans[j].exists("notch_q") && chans[j]["notch"].getType() != chans[j]["notch_q"].getType()) { - cerr<<"Configuration error: devices.["<freq_count<<" elements\n"; - error(); - } - if(libconfig::Setting::TypeList == chans[j]["notch"].getType()) { - for(int f = 0; ffreq_count; f++) { - float freq = (float)chans[j]["notch"][f]; - float q = chans[j].exists("notch_q") ? (float)chans[j]["notch_q"][f] : default_q; - - if (q == 0.0) { - q = default_q; - } else if (q <= 0.0) { - cerr<<"Configuration error: devices.["<freqlist[f].notch_filter = NotchFilter(freq, WAVE_RATE, q); - } - } - } else if(libconfig::Setting::TypeFloat == chans[j]["notch"].getType() ) { - float freq = (float)chans[j]["notch"]; - float q = chans[j].exists("notch_q") ? (float)chans[j]["notch_q"] : default_q; - if (q <= 0.0) { - cerr<<"Configuration error: devices.["<freq_count; f++) { - if(freq == 0) { - continue; // "disable" is default so ignore without error message - } else if(freq < 0) { - cerr << "devices.["<freqlist[f].notch_filter = NotchFilter(freq, WAVE_RATE, q); - } - } - } else { - cerr<<"Configuration error: devices.["<freq_count<<" elements\n"; - error(); - } - } - if(chans[j].exists("ctcss")) { - if(libconfig::Setting::TypeList == chans[j]["ctcss"].getType()) { - for(int f = 0; ffreq_count; f++) { - float freq = (float)chans[j]["ctcss"][f]; - - if(freq == 0) { - continue; // "disable" for this channel in list - } else if(freq < 0) { - cerr << "devices.["<freqlist[f].squelch.set_ctcss_freq(freq, WAVE_RATE); - } - } - } else if(libconfig::Setting::TypeFloat == chans[j]["ctcss"].getType() ) { - float freq = (float)chans[j]["ctcss"]; - for(int f = 0; ffreq_count; f++) { - if(freq <= 0) { - cerr << "devices.["<freqlist[f].squelch.set_ctcss_freq(freq, WAVE_RATE); - } - } - } else { - cerr<<"Configuration error: devices.["<freq_count<<" elements\n"; - error(); - } - } - if(chans[j].exists("bandwidth")) { - channel->needs_raw_iq = 1; - - if(libconfig::Setting::TypeList == chans[j]["bandwidth"].getType()) { - for(int f = 0; ffreq_count; f++) { - int bandwidth = parse_anynum2int(chans[j]["bandwidth"][f]); - - if(bandwidth == 0) { - continue; // "disable" for this channel in list - } else if(bandwidth < 0) { - cerr << "devices.["<freqlist[f].lowpass_filter = LowpassFilter((float)bandwidth/2, WAVE_RATE); - } - } - } else { - int bandwidth = parse_anynum2int(chans[j]["bandwidth"]); - if(bandwidth == 0) { - continue; // "disable" is default so ignore without error message - } else if(bandwidth < 0) { - cerr << "devices.["<freq_count; f++) { - channel->freqlist[f].lowpass_filter = LowpassFilter((float)bandwidth/2, WAVE_RATE); - } - } - } - } - if(chans[j].exists("ampfactor")) { - if(libconfig::Setting::TypeList == chans[j]["ampfactor"].getType()) { - for(int f = 0; f < channel->freq_count; f++) { - float ampfactor = (float)chans[j]["ampfactor"][f]; - - if(ampfactor < 0) { - cerr << "devices.["<freqlist[f].ampfactor = ampfactor; - } - } else { - float ampfactor = (float)chans[j]["ampfactor"]; - - if(ampfactor < 0) { - cerr << "devices.["<freq_count; f++) { - channel->freqlist[f].ampfactor = ampfactor; - } - } - } + if (strncmp(chans[j]["modulations"][f], "am", 2) == 0) { + channel->freqlist[f].modulation = MOD_AM; + } else { + cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "] modulations.[" << f << "]: unknown modulation\n"; + error(); + } + } else { + channel->freqlist[f].modulation = channel_modulation; + } + } + // Set initial frequency for scanning + // We tune 20 FFT bins higher to avoid DC spike + dev->input->centerfreq = channel->freqlist[0].frequency + 20 * (double)(dev->input->sample_rate / fft_size); + } + if (chans[j].exists("squelch")) { + cerr << "Warning: 'squelch' no longer supported and will be ignored, use 'squelch_threshold' or 'squelch_snr_threshold' instead\n"; + } + if (chans[j].exists("squelch_threshold") && chans[j].exists("squelch_snr_threshold")) { + cerr << "Warning: Both 'squelch_threshold' and 'squelch_snr_threshold' are set and may conflict\n"; + } + if (chans[j].exists("squelch_threshold")) { + // Value is dBFS, zero disables manual threshold (ie use auto squelch), negative is valid, positive is invalid + if (libconfig::Setting::TypeList == chans[j]["squelch_threshold"].getType()) { + // New-style array of per-frequency squelch settings + for (int f = 0; f < channel->freq_count; f++) { + int threshold_dBFS = (int)chans[j]["squelch_threshold"][f]; + if (threshold_dBFS > 0) { + cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "]: squelch_threshold must be less than or equal to 0\n"; + error(); + } else if (threshold_dBFS == 0) { + channel->freqlist[f].squelch.set_squelch_level_threshold(0); + } else { + channel->freqlist[f].squelch.set_squelch_level_threshold(dBFS_to_level(threshold_dBFS)); + } + } + } else if (libconfig::Setting::TypeInt == chans[j]["squelch_threshold"].getType()) { + // Legacy (single squelch for all frequencies) + int threshold_dBFS = (int)chans[j]["squelch_threshold"]; + float level; + if (threshold_dBFS > 0) { + cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "]: squelch_threshold must be less than or equal to 0\n"; + error(); + } else if (threshold_dBFS == 0) { + level = 0; + } else { + level = dBFS_to_level(threshold_dBFS); + } + + for (int f = 0; f < channel->freq_count; f++) { + channel->freqlist[f].squelch.set_squelch_level_threshold(level); + } + } else { + cerr << "Invalid value for squelch_threshold (should be int or list - use parentheses)\n"; + error(); + } + } + if (chans[j].exists("squelch_snr_threshold")) { + // Value is SNR in dB, zero disables squelch (ie always open), -1 uses default value, positive is valid, other negative values are invalid + if (libconfig::Setting::TypeList == chans[j]["squelch_snr_threshold"].getType()) { + // New-style array of per-frequency squelch settings + for (int f = 0; f < channel->freq_count; f++) { + float snr = 0.f; + if (libconfig::Setting::TypeFloat == chans[j]["squelch_snr_threshold"][f].getType()) { + snr = (float)chans[j]["squelch_snr_threshold"][f]; + } else if (libconfig::Setting::TypeInt == chans[j]["squelch_snr_threshold"][f].getType()) { + snr = (int)chans[j]["squelch_snr_threshold"][f]; + } else { + cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "]: squelch_snr_threshold list must be of int or float\n"; + error(); + } + + if (snr == -1.0) { + continue; // "disable" for this channel in list + } else if (snr < 0) { + cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "]: squelch_snr_threshold must be greater than or equal to 0\n"; + error(); + } else { + channel->freqlist[f].squelch.set_squelch_snr_threshold(snr); + } + } + } else if (libconfig::Setting::TypeFloat == chans[j]["squelch_snr_threshold"].getType() || libconfig::Setting::TypeInt == chans[j]["squelch_snr_threshold"].getType()) { + // Legacy (single squelch for all frequencies) + float snr = (libconfig::Setting::TypeFloat == chans[j]["squelch_snr_threshold"].getType()) ? (float)chans[j]["squelch_snr_threshold"] : (int)chans[j]["squelch_snr_threshold"]; + + if (snr == -1.0) { + continue; // "disable" so use the default without error message + } else if (snr < 0) { + cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "]: squelch_snr_threshold must be greater than or equal to 0\n"; + error(); + } + + for (int f = 0; f < channel->freq_count; f++) { + channel->freqlist[f].squelch.set_squelch_snr_threshold(snr); + } + } else { + cerr << "Invalid value for squelch_snr_threshold (should be float, int, or list of int/float - use parentheses)\n"; + error(); + } + } + if (chans[j].exists("notch")) { + static const float default_q = 10.0; + + if (chans[j].exists("notch_q") && chans[j]["notch"].getType() != chans[j]["notch_q"].getType()) { + cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "]: notch_q (if set) must be the same type as notch - " + << "float or a list of floats with at least " << channel->freq_count << " elements\n"; + error(); + } + if (libconfig::Setting::TypeList == chans[j]["notch"].getType()) { + for (int f = 0; f < channel->freq_count; f++) { + float freq = (float)chans[j]["notch"][f]; + float q = chans[j].exists("notch_q") ? (float)chans[j]["notch_q"][f] : default_q; + + if (q == 0.0) { + q = default_q; + } else if (q <= 0.0) { + cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "] freq.[" << f << "]: invalid value for notch_q: " << q << " (must be greater than 0.0)\n"; + error(); + } + + if (freq == 0) { + continue; // "disable" for this channel in list + } else if (freq < 0) { + cerr << "devices.[" << i << "] channels.[" << j << "] freq.[" << f << "]: invalid value for notch: " << freq << ", ignoring\n"; + } else { + channel->freqlist[f].notch_filter = NotchFilter(freq, WAVE_RATE, q); + } + } + } else if (libconfig::Setting::TypeFloat == chans[j]["notch"].getType()) { + float freq = (float)chans[j]["notch"]; + float q = chans[j].exists("notch_q") ? (float)chans[j]["notch_q"] : default_q; + if (q <= 0.0) { + cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "]: invalid value for notch_q: " << q << " (must be greater than 0.0)\n"; + error(); + } + for (int f = 0; f < channel->freq_count; f++) { + if (freq == 0) { + continue; // "disable" is default so ignore without error message + } else if (freq < 0) { + cerr << "devices.[" << i << "] channels.[" << j << "]: notch value '" << freq << "' invalid, ignoring\n"; + } else { + channel->freqlist[f].notch_filter = NotchFilter(freq, WAVE_RATE, q); + } + } + } else { + cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "]: notch should be an float or a list of floats with at least " << channel->freq_count << " elements\n"; + error(); + } + } + if (chans[j].exists("ctcss")) { + if (libconfig::Setting::TypeList == chans[j]["ctcss"].getType()) { + for (int f = 0; f < channel->freq_count; f++) { + float freq = (float)chans[j]["ctcss"][f]; + + if (freq == 0) { + continue; // "disable" for this channel in list + } else if (freq < 0) { + cerr << "devices.[" << i << "] channels.[" << j << "] freq.[" << f << "]: invalid value for ctcss: " << freq << ", ignoring\n"; + } else { + channel->freqlist[f].squelch.set_ctcss_freq(freq, WAVE_RATE); + } + } + } else if (libconfig::Setting::TypeFloat == chans[j]["ctcss"].getType()) { + float freq = (float)chans[j]["ctcss"]; + for (int f = 0; f < channel->freq_count; f++) { + if (freq <= 0) { + cerr << "devices.[" << i << "] channels.[" << j << "]: ctcss value '" << freq << "' invalid, ignoring\n"; + } else { + channel->freqlist[f].squelch.set_ctcss_freq(freq, WAVE_RATE); + } + } + } else { + cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "]: ctcss should be an float or a list of floats with at least " << channel->freq_count << " elements\n"; + error(); + } + } + if (chans[j].exists("bandwidth")) { + channel->needs_raw_iq = 1; + + if (libconfig::Setting::TypeList == chans[j]["bandwidth"].getType()) { + for (int f = 0; f < channel->freq_count; f++) { + int bandwidth = parse_anynum2int(chans[j]["bandwidth"][f]); + + if (bandwidth == 0) { + continue; // "disable" for this channel in list + } else if (bandwidth < 0) { + cerr << "devices.[" << i << "] channels.[" << j << "] freq.[" << f << "]: bandwidth value '" << bandwidth << "' invalid, ignoring\n"; + } else { + channel->freqlist[f].lowpass_filter = LowpassFilter((float)bandwidth / 2, WAVE_RATE); + } + } + } else { + int bandwidth = parse_anynum2int(chans[j]["bandwidth"]); + if (bandwidth == 0) { + continue; // "disable" is default so ignore without error message + } else if (bandwidth < 0) { + cerr << "devices.[" << i << "] channels.[" << j << "]: bandwidth value '" << bandwidth << "' invalid, ignoring\n"; + } else { + for (int f = 0; f < channel->freq_count; f++) { + channel->freqlist[f].lowpass_filter = LowpassFilter((float)bandwidth / 2, WAVE_RATE); + } + } + } + } + if (chans[j].exists("ampfactor")) { + if (libconfig::Setting::TypeList == chans[j]["ampfactor"].getType()) { + for (int f = 0; f < channel->freq_count; f++) { + float ampfactor = (float)chans[j]["ampfactor"][f]; + + if (ampfactor < 0) { + cerr << "devices.[" << i << "] channels.[" << j << "] freq.[" << f << "]: ampfactor '" << ampfactor << "' must not be negative\n"; + error(); + } + + channel->freqlist[f].ampfactor = ampfactor; + } + } else { + float ampfactor = (float)chans[j]["ampfactor"]; + + if (ampfactor < 0) { + cerr << "devices.[" << i << "] channels.[" << j << "]: ampfactor '" << ampfactor << "' must not be negative\n"; + error(); + } + + for (int f = 0; f < channel->freq_count; f++) { + channel->freqlist[f].ampfactor = ampfactor; + } + } + } #ifdef NFM - if(chans[j].exists("tau")) { - channel->alpha = ((int)chans[j]["tau"] == 0 ? 0.0f : exp(-1.0f/(WAVE_RATE * 1e-6 * (int)chans[j]["tau"]))); - } + if (chans[j].exists("tau")) { + channel->alpha = ((int)chans[j]["tau"] == 0 ? 0.0f : exp(-1.0f / (WAVE_RATE * 1e-6 * (int)chans[j]["tau"]))); + } #endif /* NFM */ - libconfig::Setting &outputs = chans[j]["outputs"]; - channel->output_count = outputs.getLength(); - if(channel->output_count < 1) { - cerr<<"Configuration error: devices.["<outputs = (output_t *)XCALLOC(channel->output_count, sizeof(struct output_t)); - int outputs_enabled = parse_outputs(outputs, channel, i, j, false); - if(outputs_enabled < 1) { - cerr<<"Configuration error: devices.["<outputs = (output_t *)XREALLOC(channel->outputs, outputs_enabled * sizeof(struct output_t)); - channel->output_count = outputs_enabled; - - dev->base_bins[jj] = dev->bins[jj] = (size_t)ceil( - (channel->freqlist[0].frequency + dev->input->sample_rate - dev->input->centerfreq) - / (double)(dev->input->sample_rate / fft_size) - 1.0 - ) % fft_size; - debug_print("bins[%d]: %zu\n", jj, dev->bins[jj]); + libconfig::Setting& outputs = chans[j]["outputs"]; + channel->output_count = outputs.getLength(); + if (channel->output_count < 1) { + cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "]: no outputs defined\n"; + error(); + } + channel->outputs = (output_t*)XCALLOC(channel->output_count, sizeof(struct output_t)); + int outputs_enabled = parse_outputs(outputs, channel, i, j, false); + if (outputs_enabled < 1) { + cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "]: no outputs defined\n"; + error(); + } + channel->outputs = (output_t*)XREALLOC(channel->outputs, outputs_enabled * sizeof(struct output_t)); + channel->output_count = outputs_enabled; + + dev->base_bins[jj] = dev->bins[jj] = + (size_t)ceil((channel->freqlist[0].frequency + dev->input->sample_rate - dev->input->centerfreq) / (double)(dev->input->sample_rate / fft_size) - 1.0) % fft_size; + debug_print("bins[%d]: %zu\n", jj, dev->bins[jj]); #ifdef NFM - for(int f = 0; f < channel->freq_count; f++) { - if(channel->freqlist[f].modulation == MOD_NFM) { - channel->needs_raw_iq = 1; - break; - } - } + for (int f = 0; f < channel->freq_count; f++) { + if (channel->freqlist[f].modulation == MOD_NFM) { + channel->needs_raw_iq = 1; + break; + } + } #endif /* NFM */ - if(channel->needs_raw_iq) { -// Downmixing is done only for NFM and raw IQ outputs. It's not critical to have some residual -// freq offset in AM, as it doesn't affect sound quality significantly. - double dm_dphi = (double)(channel->freqlist[0].frequency - dev->input->centerfreq); // downmix freq in Hz - -// In general, sample_rate is not required to be an integer multiple of WAVE_RATE. -// However the FFT window may only slide by an integer number of input samples. A non-zero rounding error -// introduces additional phase rotation which we have to compensate in order to shift the channel of interest -// to the center of the spectrum of the output I/Q stream. This is important for correct NFM demodulation. -// The error value (in Hz): -// - has an absolute value 0..WAVE_RATE/2 -// - is linear with the error introduced by rounding the value of sample_rate/WAVE_RATE to the nearest integer -// (range of -0.5..0.5) -// - is linear with the distance between center frequency and the channel frequency, normalized to 0..1 - double decimation_factor = ((double)dev->input->sample_rate / (double)WAVE_RATE); - double dm_dphi_correction = (double)WAVE_RATE / 2.0; - dm_dphi_correction *= (decimation_factor - round(decimation_factor)); - dm_dphi_correction *= (double)(channel->freqlist[0].frequency - dev->input->centerfreq) / - ((double)dev->input->sample_rate/2.0); - - debug_print("dev[%d].chan[%d]: dm_dphi: %f Hz dm_dphi_correction: %f Hz\n", - i, jj, dm_dphi, dm_dphi_correction); - dm_dphi -= dm_dphi_correction; - debug_print("dev[%d].chan[%d]: dm_dphi_corrected: %f Hz\n", i, jj, dm_dphi); -// Normalize - dm_dphi /= (double)WAVE_RATE; -// Unalias it, to prevent overflow of int during cast - dm_dphi -= trunc(dm_dphi); - debug_print("dev[%d].chan[%d]: dm_dphi_normalized=%f\n", i, jj, dm_dphi); -// Translate this to uint32_t range 0x00000000-0x00ffffff - dm_dphi *= 256.0 * 65536.0; -// Cast it to signed int first, because casting negative float to uint is not portable - channel->dm_dphi = (uint32_t)((int)dm_dphi); - debug_print("dev[%d].chan[%d]: dm_dphi_scaled=%f cast=0x%x\n", i, jj, dm_dphi, channel->dm_dphi); - channel->dm_phi = 0.f; - } + if (channel->needs_raw_iq) { + // Downmixing is done only for NFM and raw IQ outputs. It's not critical to have some residual + // freq offset in AM, as it doesn't affect sound quality significantly. + double dm_dphi = (double)(channel->freqlist[0].frequency - dev->input->centerfreq); // downmix freq in Hz + + // In general, sample_rate is not required to be an integer multiple of WAVE_RATE. + // However the FFT window may only slide by an integer number of input samples. A non-zero rounding error + // introduces additional phase rotation which we have to compensate in order to shift the channel of interest + // to the center of the spectrum of the output I/Q stream. This is important for correct NFM demodulation. + // The error value (in Hz): + // - has an absolute value 0..WAVE_RATE/2 + // - is linear with the error introduced by rounding the value of sample_rate/WAVE_RATE to the nearest integer + // (range of -0.5..0.5) + // - is linear with the distance between center frequency and the channel frequency, normalized to 0..1 + double decimation_factor = ((double)dev->input->sample_rate / (double)WAVE_RATE); + double dm_dphi_correction = (double)WAVE_RATE / 2.0; + dm_dphi_correction *= (decimation_factor - round(decimation_factor)); + dm_dphi_correction *= (double)(channel->freqlist[0].frequency - dev->input->centerfreq) / ((double)dev->input->sample_rate / 2.0); + + debug_print("dev[%d].chan[%d]: dm_dphi: %f Hz dm_dphi_correction: %f Hz\n", i, jj, dm_dphi, dm_dphi_correction); + dm_dphi -= dm_dphi_correction; + debug_print("dev[%d].chan[%d]: dm_dphi_corrected: %f Hz\n", i, jj, dm_dphi); + // Normalize + dm_dphi /= (double)WAVE_RATE; + // Unalias it, to prevent overflow of int during cast + dm_dphi -= trunc(dm_dphi); + debug_print("dev[%d].chan[%d]: dm_dphi_normalized=%f\n", i, jj, dm_dphi); + // Translate this to uint32_t range 0x00000000-0x00ffffff + dm_dphi *= 256.0 * 65536.0; + // Cast it to signed int first, because casting negative float to uint is not portable + channel->dm_dphi = (uint32_t)((int)dm_dphi); + debug_print("dev[%d].chan[%d]: dm_dphi_scaled=%f cast=0x%x\n", i, jj, dm_dphi, channel->dm_dphi); + channel->dm_phi = 0.f; + } #ifdef DEBUG_SQUELCH - // Setup squelch debug file, if enabled - char tmp_filepath[1024]; - for(int f = 0; f < channel->freq_count; f++) { - snprintf(tmp_filepath, sizeof(tmp_filepath), "./squelch_debug-%d-%d.dat", j, f); - channel->freqlist[f].squelch.set_debug_file(tmp_filepath); - } + // Setup squelch debug file, if enabled + char tmp_filepath[1024]; + for (int f = 0; f < channel->freq_count; f++) { + snprintf(tmp_filepath, sizeof(tmp_filepath), "./squelch_debug-%d-%d.dat", j, f); + channel->freqlist[f].squelch.set_debug_file(tmp_filepath); + } #endif /* DEBUG_SQUELCH */ - jj++; - } - return jj; + jj++; + } + return jj; } -int parse_devices(libconfig::Setting &devs) { - int devcnt = 0; - for (int i = 0; i < devs.getLength(); i++) { - if(devs[i].exists("disable") && (bool)devs[i]["disable"] == true) continue; - device_t* dev = devices + devcnt; - if(devs[i].exists("type")) { - dev->input = input_new(devs[i]["type"]); - if(dev->input == NULL) { - cerr<<"Configuration error: devices.["<input = input_new(devs[i]["type"]); + if (dev->input == NULL) { + cerr << "Configuration error: devices.[" << i << "]: unsupported device type\n"; + error(); + } + } else { #ifdef WITH_RTLSDR - cerr<<"Warning: devices.["<input = input_new("rtlsdr"); + cerr << "Warning: devices.[" << i << "]: assuming device type \"rtlsdr\", please set \"type\" in the device section.\n"; + dev->input = input_new("rtlsdr"); #else - cerr<<"Configuration error: devices.["<input != NULL); - if(devs[i].exists("sample_rate")) { - int sample_rate = parse_anynum2int(devs[i]["sample_rate"]); - if(sample_rate < WAVE_RATE) { - cerr<<"Configuration error: devices.["<input->sample_rate = sample_rate; - } - if(devs[i].exists("mode")) { - if(!strncmp(devs[i]["mode"], "multichannel", 12)) { - dev->mode = R_MULTICHANNEL; - } else if(!strncmp(devs[i]["mode"], "scan", 4)) { - dev->mode = R_SCAN; - } else { - cerr<<"Configuration error: devices.["<mode = R_MULTICHANNEL; - } - if(dev->mode == R_MULTICHANNEL) { - dev->input->centerfreq = parse_anynum2int(devs[i]["centerfreq"]); - } // centerfreq for R_SCAN will be set by parse_channels() after frequency list has been read + } + assert(dev->input != NULL); + if (devs[i].exists("sample_rate")) { + int sample_rate = parse_anynum2int(devs[i]["sample_rate"]); + if (sample_rate < WAVE_RATE) { + cerr << "Configuration error: devices.[" << i << "]: sample_rate must be greater than " << WAVE_RATE << "\n"; + error(); + } + dev->input->sample_rate = sample_rate; + } + if (devs[i].exists("mode")) { + if (!strncmp(devs[i]["mode"], "multichannel", 12)) { + dev->mode = R_MULTICHANNEL; + } else if (!strncmp(devs[i]["mode"], "scan", 4)) { + dev->mode = R_SCAN; + } else { + cerr << "Configuration error: devices.[" << i << "]: invalid mode (must be one of: \"scan\", \"multichannel\")\n"; + error(); + } + } else { + dev->mode = R_MULTICHANNEL; + } + if (dev->mode == R_MULTICHANNEL) { + dev->input->centerfreq = parse_anynum2int(devs[i]["centerfreq"]); + } // centerfreq for R_SCAN will be set by parse_channels() after frequency list has been read #ifdef NFM - if(devs[i].exists("tau")) { - dev->alpha = ((int)devs[i]["tau"] == 0 ? 0.0f : exp(-1.0f/(WAVE_RATE * 1e-6 * (int)devs[i]["tau"]))); - } else { - dev->alpha = alpha; - } + if (devs[i].exists("tau")) { + dev->alpha = ((int)devs[i]["tau"] == 0 ? 0.0f : exp(-1.0f / (WAVE_RATE * 1e-6 * (int)devs[i]["tau"]))); + } else { + dev->alpha = alpha; + } #endif /* NFM */ -// Parse hardware-dependent configuration parameters - if(input_parse_config(dev->input, devs[i]) < 0) { - // FIXME: get and display error string from input_parse_config - // Right now it exits the program on failure. - } -// Some basic sanity checks for crucial parameters which have to be set -// (or can be modified) by the input driver - assert(dev->input->sfmt != SFMT_UNDEF); - assert(dev->input->fullscale > 0); - assert(dev->input->bytes_per_sample > 0); - assert(dev->input->sample_rate > WAVE_RATE); - -// For the input buffer size use a base value and round it up to the nearest multiple -// of FFT_BATCH blocks of input samples. -// ceil is required here because sample rate is not guaranteed to be an integer multiple of WAVE_RATE. - size_t fft_batch_len = FFT_BATCH * (2 * dev->input->bytes_per_sample * - (size_t)ceil((double)dev->input->sample_rate / (double)WAVE_RATE)); - dev->input->buf_size = MIN_BUF_SIZE; - if(dev->input->buf_size % fft_batch_len != 0) - dev->input->buf_size += fft_batch_len - dev->input->buf_size % fft_batch_len; - debug_print("dev->input->buf_size: %zu\n", dev->input->buf_size); - dev->input->buffer = (unsigned char *)XCALLOC(sizeof(unsigned char), - dev->input->buf_size + 2 * dev->input->bytes_per_sample * fft_size); - dev->input->bufs = dev->input->bufe = 0; - dev->input->overflow_count = 0; - dev->output_overrun_count = 0; - dev->waveend = dev->waveavail = dev->row = dev->tq_head = dev->tq_tail = 0; - dev->last_frequency = -1; - - libconfig::Setting &chans = devs[i]["channels"]; - if(chans.getLength() < 1) { - cerr<<"Configuration error: devices.["<channels = (channel_t *)XCALLOC(chans.getLength(), sizeof(channel_t)); - dev->bins = (size_t *)XCALLOC(chans.getLength(), sizeof(size_t)); - dev->base_bins = (size_t *)XCALLOC(chans.getLength(), sizeof(size_t)); - dev->channel_count = 0; - int channel_count = parse_channels(chans, dev, i); - if(channel_count < 1) { - cerr<<"Configuration error: devices.["<mode == R_SCAN && channel_count > 1) { - cerr<<"Configuration error: devices.["<channels = (channel_t *)XREALLOC(dev->channels, channel_count * sizeof(channel_t)); - dev->bins = (size_t *)XREALLOC(dev->bins, channel_count * sizeof(size_t)); - dev->base_bins = (size_t *)XREALLOC(dev->base_bins, channel_count * sizeof(size_t)); - dev->channel_count = channel_count; - devcnt++; - } - return devcnt; + // Parse hardware-dependent configuration parameters + if (input_parse_config(dev->input, devs[i]) < 0) { + // FIXME: get and display error string from input_parse_config + // Right now it exits the program on failure. + } + // Some basic sanity checks for crucial parameters which have to be set + // (or can be modified) by the input driver + assert(dev->input->sfmt != SFMT_UNDEF); + assert(dev->input->fullscale > 0); + assert(dev->input->bytes_per_sample > 0); + assert(dev->input->sample_rate > WAVE_RATE); + + // For the input buffer size use a base value and round it up to the nearest multiple + // of FFT_BATCH blocks of input samples. + // ceil is required here because sample rate is not guaranteed to be an integer multiple of WAVE_RATE. + size_t fft_batch_len = FFT_BATCH * (2 * dev->input->bytes_per_sample * (size_t)ceil((double)dev->input->sample_rate / (double)WAVE_RATE)); + dev->input->buf_size = MIN_BUF_SIZE; + if (dev->input->buf_size % fft_batch_len != 0) + dev->input->buf_size += fft_batch_len - dev->input->buf_size % fft_batch_len; + debug_print("dev->input->buf_size: %zu\n", dev->input->buf_size); + dev->input->buffer = (unsigned char*)XCALLOC(sizeof(unsigned char), dev->input->buf_size + 2 * dev->input->bytes_per_sample * fft_size); + dev->input->bufs = dev->input->bufe = 0; + dev->input->overflow_count = 0; + dev->output_overrun_count = 0; + dev->waveend = dev->waveavail = dev->row = dev->tq_head = dev->tq_tail = 0; + dev->last_frequency = -1; + + libconfig::Setting& chans = devs[i]["channels"]; + if (chans.getLength() < 1) { + cerr << "Configuration error: devices.[" << i << "]: no channels configured\n"; + error(); + } + dev->channels = (channel_t*)XCALLOC(chans.getLength(), sizeof(channel_t)); + dev->bins = (size_t*)XCALLOC(chans.getLength(), sizeof(size_t)); + dev->base_bins = (size_t*)XCALLOC(chans.getLength(), sizeof(size_t)); + dev->channel_count = 0; + int channel_count = parse_channels(chans, dev, i); + if (channel_count < 1) { + cerr << "Configuration error: devices.[" << i << "]: no channels enabled\n"; + error(); + } + if (dev->mode == R_SCAN && channel_count > 1) { + cerr << "Configuration error: devices.[" << i << "]: only one channel is allowed in scan mode\n"; + error(); + } + dev->channels = (channel_t*)XREALLOC(dev->channels, channel_count * sizeof(channel_t)); + dev->bins = (size_t*)XREALLOC(dev->bins, channel_count * sizeof(size_t)); + dev->base_bins = (size_t*)XREALLOC(dev->base_bins, channel_count * sizeof(size_t)); + dev->channel_count = channel_count; + devcnt++; + } + return devcnt; } -int parse_mixers(libconfig::Setting &mx) { - const char *name; - int mm = 0; - for(int i = 0; i < mx.getLength(); i++) { - if(mx[i].exists("disable") && (bool)mx[i]["disable"] == true) continue; - if((name = mx[i].getName()) == NULL) { - cerr<<"Configuration error: mixers.["<name = strdup(name); - mixer->enabled = false; - mixer->interval = MIX_DIVISOR; - mixer->output_overrun_count = 0; - mixer->input_count = 0; - mixer->inputs = NULL; - mixer->inputs_todo = NULL; - mixer->input_mask = NULL; - channel_t *channel = &mixer->channel; - channel->highpass = mx[i].exists("highpass") ? (int)mx[i]["highpass"] : 100; - channel->lowpass = mx[i].exists("lowpass") ? (int)mx[i]["lowpass"] : 2500; - channel->mode = MM_MONO; - - // Make sure lowpass / highpass aren't flipped. - // If lowpass is enabled (greater than zero) it must be larger than highpass - if (channel->lowpass > 0 && channel->lowpass < channel->highpass) { - cerr << "Configuration error: mixers.[" << i <<"]: lowpass (" << channel->lowpass << ") must be greater than or equal to highpass (" << channel->highpass << ")\n"; - error(); - } - - libconfig::Setting &outputs = mx[i]["outputs"]; - channel->output_count = outputs.getLength(); - if(channel->output_count < 1) { - cerr<<"Configuration error: mixers.["<outputs = (output_t *)XCALLOC(channel->output_count, sizeof(struct output_t)); - int outputs_enabled = parse_outputs(outputs, channel, i, 0, true); - if(outputs_enabled < 1) { - cerr<<"Configuration error: mixers.["<outputs = (output_t *)XREALLOC(channel->outputs, outputs_enabled * sizeof(struct output_t)); - channel->output_count = outputs_enabled; - mm++; - } - return mm; +int parse_mixers(libconfig::Setting& mx) { + const char* name; + int mm = 0; + for (int i = 0; i < mx.getLength(); i++) { + if (mx[i].exists("disable") && (bool)mx[i]["disable"] == true) + continue; + if ((name = mx[i].getName()) == NULL) { + cerr << "Configuration error: mixers.[" << i << "]: undefined mixer name\n"; + error(); + } + debug_print("mm=%d name=%s\n", mm, name); + mixer_t* mixer = &mixers[mm]; + mixer->name = strdup(name); + mixer->enabled = false; + mixer->interval = MIX_DIVISOR; + mixer->output_overrun_count = 0; + mixer->input_count = 0; + mixer->inputs = NULL; + mixer->inputs_todo = NULL; + mixer->input_mask = NULL; + channel_t* channel = &mixer->channel; + channel->highpass = mx[i].exists("highpass") ? (int)mx[i]["highpass"] : 100; + channel->lowpass = mx[i].exists("lowpass") ? (int)mx[i]["lowpass"] : 2500; + channel->mode = MM_MONO; + + // Make sure lowpass / highpass aren't flipped. + // If lowpass is enabled (greater than zero) it must be larger than highpass + if (channel->lowpass > 0 && channel->lowpass < channel->highpass) { + cerr << "Configuration error: mixers.[" << i << "]: lowpass (" << channel->lowpass << ") must be greater than or equal to highpass (" << channel->highpass << ")\n"; + error(); + } + + libconfig::Setting& outputs = mx[i]["outputs"]; + channel->output_count = outputs.getLength(); + if (channel->output_count < 1) { + cerr << "Configuration error: mixers.[" << i << "]: no outputs defined\n"; + error(); + } + channel->outputs = (output_t*)XCALLOC(channel->output_count, sizeof(struct output_t)); + int outputs_enabled = parse_outputs(outputs, channel, i, 0, true); + if (outputs_enabled < 1) { + cerr << "Configuration error: mixers.[" << i << "]: no outputs defined\n"; + error(); + } + channel->outputs = (output_t*)XREALLOC(channel->outputs, outputs_enabled * sizeof(struct output_t)); + channel->output_count = outputs_enabled; + mm++; + } + return mm; } // vim: ts=4 diff --git a/src/ctcss.h b/src/ctcss.h index 432a44d..94d080c 100644 --- a/src/ctcss.h +++ b/src/ctcss.h @@ -20,82 +20,79 @@ #ifndef _CTCSS_H #define _CTCSS_H 1 +#include // size_t #include -#include // size_t class ToneDetector { -public: - ToneDetector(float tone_freq, float sample_freq, int window_size); - void process_sample(const float &sample); - void reset(void); - - const float & relative_power(void) const { return magnitude_; } - const float & freq(void) const { return tone_freq_; } - const float & coefficient (void) const { return coeff_; } - -private: - float tone_freq_; - float magnitude_; - - int window_size_; - float coeff_; - - int count_; - float q0_; - float q1_; - float q2_; + public: + ToneDetector(float tone_freq, float sample_freq, int window_size); + void process_sample(const float& sample); + void reset(void); + + const float& relative_power(void) const { return magnitude_; } + const float& freq(void) const { return tone_freq_; } + const float& coefficient(void) const { return coeff_; } + + private: + float tone_freq_; + float magnitude_; + + int window_size_; + float coeff_; + + int count_; + float q0_; + float q1_; + float q2_; }; - class ToneDetectorSet { -public: - struct PowerIndex { - float power; - float freq; - }; - - ToneDetectorSet() {} - - bool add(const float & tone_freq, const float & sample_freq, int window_size); - void process_sample(const float &sample); - void reset(void); - - float sorted_powers(std::vector &powers); - -private: - std::vector tones_; -}; + public: + struct PowerIndex { + float power; + float freq; + }; + + ToneDetectorSet() {} + bool add(const float& tone_freq, const float& sample_freq, int window_size); + void process_sample(const float& sample); + void reset(void); + + float sorted_powers(std::vector& powers); + + private: + std::vector tones_; +}; class CTCSS { -public: - CTCSS(void) : enabled_(false), found_count_(0), not_found_count_(0) {} - CTCSS(const float & ctcss_freq, const float & sample_rate, int window_size); - void process_audio_sample(const float &sample); - void reset(void); - - const size_t & found_count(void) const { return found_count_; } - const size_t & not_found_count(void) const { return not_found_count_; } - - bool is_enabled(void) const { return enabled_; } - bool enough_samples(void) const { return enough_samples_; } - bool has_tone(void) const { return !enabled_ || has_tone_; } - - static std::vector standard_tones; - -private: - bool enabled_; - float ctcss_freq_; - int window_size_; - size_t found_count_; - size_t not_found_count_; - - ToneDetectorSet powers_; - - bool enough_samples_; - int sample_count_; - bool has_tone_; + public: + CTCSS(void) : enabled_(false), found_count_(0), not_found_count_(0) {} + CTCSS(const float& ctcss_freq, const float& sample_rate, int window_size); + void process_audio_sample(const float& sample); + void reset(void); + + const size_t& found_count(void) const { return found_count_; } + const size_t& not_found_count(void) const { return not_found_count_; } + + bool is_enabled(void) const { return enabled_; } + bool enough_samples(void) const { return enough_samples_; } + bool has_tone(void) const { return !enabled_ || has_tone_; } + + static std::vector standard_tones; + + private: + bool enabled_; + float ctcss_freq_; + int window_size_; + size_t found_count_; + size_t not_found_count_; + + ToneDetectorSet powers_; + bool enough_samples_; + int sample_count_; + bool has_tone_; }; #endif /* _CTCSS_H */ diff --git a/src/filters.cpp b/src/filters.cpp index 426e171..be7a926 100644 --- a/src/filters.cpp +++ b/src/filters.cpp @@ -17,161 +17,147 @@ * along with this program. If not, see . */ -#include "logging.h" // debug_print() +#include "logging.h" // debug_print() #include "filters.h" using namespace std; // Default constructor is no filter -NotchFilter::NotchFilter(void) : enabled_(false) { -} +NotchFilter::NotchFilter(void) : enabled_(false) {} // Notch Filter based on https://www.dsprelated.com/showcode/173.php -NotchFilter::NotchFilter(float notch_freq, float sample_freq, float q): enabled_(true), x{0.0}, y{0.0} { - if (notch_freq <= 0.0) { - debug_print("Invalid frequency %f Hz, disabling notch filter\n", notch_freq); - enabled_ = false; - return; - } +NotchFilter::NotchFilter(float notch_freq, float sample_freq, float q) : enabled_(true), x{0.0}, y{0.0} { + if (notch_freq <= 0.0) { + debug_print("Invalid frequency %f Hz, disabling notch filter\n", notch_freq); + enabled_ = false; + return; + } - debug_print("Adding notch filter for %f Hz with parameters {%f, %f}\n", notch_freq, sample_freq, q); + debug_print("Adding notch filter for %f Hz with parameters {%f, %f}\n", notch_freq, sample_freq, q); - float wo = 2*M_PI*(notch_freq/sample_freq); + float wo = 2 * M_PI * (notch_freq / sample_freq); - e = 1/(1 + tan(wo/(q*2))); - p = cos(wo); - d[0] = e; - d[1] = 2*e*p; - d[2] = (2*e-1); + e = 1 / (1 + tan(wo / (q * 2))); + p = cos(wo); + d[0] = e; + d[1] = 2 * e * p; + d[2] = (2 * e - 1); - debug_print("wo:%f e:%f p:%f d:{%f,%f,%f}\n", wo, e, p, d[0], d[1], d[2]); + debug_print("wo:%f e:%f p:%f d:{%f,%f,%f}\n", wo, e, p, d[0], d[1], d[2]); } -void NotchFilter::apply(float &value) { - if (!enabled_) { - return; - } +void NotchFilter::apply(float& value) { + if (!enabled_) { + return; + } - x[0] = x[1]; - x[1] = x[2]; - x[2] = value; + x[0] = x[1]; + x[1] = x[2]; + x[2] = value; - y[0] = y[1]; - y[1] = y[2]; - y[2] = d[0]*x[2] - d[1]*x[1] + d[0]*x[0] + d[1]*y[1] - d[2]*y[0]; + y[0] = y[1]; + y[1] = y[2]; + y[2] = d[0] * x[2] - d[1] * x[1] + d[0] * x[0] + d[1] * y[1] - d[2] * y[0]; - value = y[2]; + value = y[2]; } // Default constructor is no filter -LowpassFilter::LowpassFilter(void) : enabled_(false) { -} +LowpassFilter::LowpassFilter(void) : enabled_(false) {} // 2nd order lowpass Bessel filter, based entirely on a simplification of https://www-users.cs.york.ac.uk/~fisher/mkfilter/ LowpassFilter::LowpassFilter(float freq, float sample_freq) : enabled_(true) { - if (freq <= 0.0) { - debug_print("Invalid frequency %f Hz, disabling lowpass filter\n", freq); - enabled_ = false; - return; - } - - debug_print("Adding lowpass filter at %f Hz with a sample rate of %f\n", freq, sample_freq); - - double raw_alpha = (double)freq/sample_freq; - double warped_alpha = tan(M_PI * raw_alpha) / M_PI; - - complex zeros[2] = {-1.0, -1.0}; - complex poles[2]; - poles[0] = blt(M_PI * 2 * warped_alpha * complex(-1.10160133059e+00, 6.36009824757e-01)); - poles[1] = blt(M_PI * 2 * warped_alpha * conj(complex(-1.10160133059e+00, 6.36009824757e-01))); - - complex topcoeffs[3]; - complex botcoeffs[3]; - expand(zeros, 2, topcoeffs); - expand(poles, 2, botcoeffs); - complex gain_complex = evaluate(topcoeffs, 2, botcoeffs, 2, 1.0); - gain = hypot(gain_complex.imag(), gain_complex.real()); - - for (int i = 0; i <= 2; i++) -{ - ycoeffs[i] = -(botcoeffs[i].real() / botcoeffs[2].real()); - } - - debug_print("gain: %f, ycoeffs: {%f, %f}\n", gain, ycoeffs[0], ycoeffs[1]); + if (freq <= 0.0) { + debug_print("Invalid frequency %f Hz, disabling lowpass filter\n", freq); + enabled_ = false; + return; + } + + debug_print("Adding lowpass filter at %f Hz with a sample rate of %f\n", freq, sample_freq); + + double raw_alpha = (double)freq / sample_freq; + double warped_alpha = tan(M_PI * raw_alpha) / M_PI; + + complex zeros[2] = {-1.0, -1.0}; + complex poles[2]; + poles[0] = blt(M_PI * 2 * warped_alpha * complex(-1.10160133059e+00, 6.36009824757e-01)); + poles[1] = blt(M_PI * 2 * warped_alpha * conj(complex(-1.10160133059e+00, 6.36009824757e-01))); + + complex topcoeffs[3]; + complex botcoeffs[3]; + expand(zeros, 2, topcoeffs); + expand(poles, 2, botcoeffs); + complex gain_complex = evaluate(topcoeffs, 2, botcoeffs, 2, 1.0); + gain = hypot(gain_complex.imag(), gain_complex.real()); + + for (int i = 0; i <= 2; i++) { + ycoeffs[i] = -(botcoeffs[i].real() / botcoeffs[2].real()); + } + + debug_print("gain: %f, ycoeffs: {%f, %f}\n", gain, ycoeffs[0], ycoeffs[1]); } -complex LowpassFilter::blt(complex pz) -{ - return (2.0 + pz) / (2.0 - pz); +complex LowpassFilter::blt(complex pz) { + return (2.0 + pz) / (2.0 - pz); } /* evaluate response, substituting for z */ -complex LowpassFilter::evaluate(complex topco[], int nz, complex botco[], int np, complex z) -{ - return eval(topco, nz, z) / eval(botco, np, z); +complex LowpassFilter::evaluate(complex topco[], int nz, complex botco[], int np, complex z) { + return eval(topco, nz, z) / eval(botco, np, z); } /* evaluate polynomial in z, substituting for z */ -complex LowpassFilter::eval(complex coeffs[], int npz, complex z) -{ - complex sum (0.0); - for (int i = npz; i >= 0; i--) { - sum = (sum * z) + coeffs[i]; - } - return sum; +complex LowpassFilter::eval(complex coeffs[], int npz, complex z) { + complex sum(0.0); + for (int i = npz; i >= 0; i--) { + sum = (sum * z) + coeffs[i]; + } + return sum; } /* compute product of poles or zeros as a polynomial of z */ -void LowpassFilter::expand(complex pz[], int npz, complex coeffs[]) -{ - coeffs[0] = 1.0; - for (int i = 0; i < npz; i++) - { - coeffs[i+1] = 0.0; - } - for (int i = 0; i < npz; i++) - { - multin(pz[i], npz, coeffs); - } - /* check computed coeffs of z^k are all real */ - for (int i = 0; i < npz+1; i++) - { - if (fabs(coeffs[i].imag()) > 1e-10) - { - log(LOG_ERR, "coeff of z^%d is not real; poles/zeros are not complex conjugates\n", i); - error(); - } - } +void LowpassFilter::expand(complex pz[], int npz, complex coeffs[]) { + coeffs[0] = 1.0; + for (int i = 0; i < npz; i++) { + coeffs[i + 1] = 0.0; + } + for (int i = 0; i < npz; i++) { + multin(pz[i], npz, coeffs); + } + /* check computed coeffs of z^k are all real */ + for (int i = 0; i < npz + 1; i++) { + if (fabs(coeffs[i].imag()) > 1e-10) { + log(LOG_ERR, "coeff of z^%d is not real; poles/zeros are not complex conjugates\n", i); + error(); + } + } } -void LowpassFilter::multin(complex w, int npz, complex coeffs[]) -{ - /* multiply factor (z-w) into coeffs */ - complex nw = -w; - for (int i = npz; i >= 1; i--) - { - coeffs[i] = (nw * coeffs[i]) + coeffs[i-1]; - } - coeffs[0] = nw * coeffs[0]; +void LowpassFilter::multin(complex w, int npz, complex coeffs[]) { + /* multiply factor (z-w) into coeffs */ + complex nw = -w; + for (int i = npz; i >= 1; i--) { + coeffs[i] = (nw * coeffs[i]) + coeffs[i - 1]; + } + coeffs[0] = nw * coeffs[0]; } -void LowpassFilter::apply(float &r, float &j) { - if (!enabled_) { - return; - } +void LowpassFilter::apply(float& r, float& j) { + if (!enabled_) { + return; + } - complex input(r, j); + complex input(r, j); - xv[0] = xv[1]; - xv[1] = xv[2]; - xv[2] = input / gain; + xv[0] = xv[1]; + xv[1] = xv[2]; + xv[2] = input / gain; - yv[0] = yv[1]; - yv[1] = yv[2]; - yv[2] = (xv[0] + xv[2]) + (2.0f * xv[1]) + (ycoeffs[0] * yv[0]) + (ycoeffs[1] * yv[1]); + yv[0] = yv[1]; + yv[1] = yv[2]; + yv[2] = (xv[0] + xv[2]) + (2.0f * xv[1]) + (ycoeffs[0] * yv[0]) + (ycoeffs[1] * yv[1]); - r = yv[2].real(); - j = yv[2].imag(); + r = yv[2].real(); + j = yv[2].imag(); } - diff --git a/src/filters.h b/src/filters.h index db2e41d..209ff0d 100644 --- a/src/filters.h +++ b/src/filters.h @@ -22,45 +22,42 @@ #include -class NotchFilter -{ -public: - NotchFilter(void); - NotchFilter(float notch_freq, float sample_freq, float q); - void apply(float &value); - bool enabled(void) { return enabled_; } - -private: - bool enabled_; - float e; - float p; - float d[3]; - float x[3]; - float y[3]; +class NotchFilter { + public: + NotchFilter(void); + NotchFilter(float notch_freq, float sample_freq, float q); + void apply(float& value); + bool enabled(void) { return enabled_; } + + private: + bool enabled_; + float e; + float p; + float d[3]; + float x[3]; + float y[3]; }; -class LowpassFilter -{ -public: - LowpassFilter(void); - LowpassFilter(float freq, float sample_freq); - void apply(float &r, float &j); - bool enabled(void) const {return enabled_;} - -private: - static std::complex blt(std::complex pz); - static void expand(std::complex pz[], int npz, std::complex coeffs[]); - static void multin(std::complex w, int npz, std::complex coeffs[]); - static std::complex evaluate(std::complex topco[], int nz, std::complex botco[], int np, std::complex z); - static std::complex eval(std::complex coeffs[], int npz, std::complex z); - - bool enabled_; - float ycoeffs[3]; - float gain; - - std::complex xv[3]; - std::complex yv[3]; +class LowpassFilter { + public: + LowpassFilter(void); + LowpassFilter(float freq, float sample_freq); + void apply(float& r, float& j); + bool enabled(void) const { return enabled_; } + + private: + static std::complex blt(std::complex pz); + static void expand(std::complex pz[], int npz, std::complex coeffs[]); + static void multin(std::complex w, int npz, std::complex coeffs[]); + static std::complex evaluate(std::complex topco[], int nz, std::complex botco[], int np, std::complex z); + static std::complex eval(std::complex coeffs[], int npz, std::complex z); + + bool enabled_; + float ycoeffs[3]; + float gain; + + std::complex xv[3]; + std::complex yv[3]; }; #endif /* _FILTERS_H */ - diff --git a/src/generate_signal.cpp b/src/generate_signal.cpp index 38aab7d..f59e0f7 100644 --- a/src/generate_signal.cpp +++ b/src/generate_signal.cpp @@ -27,68 +27,60 @@ float Tone::WEAK = 0.05; float Tone::NORMAL = 0.2; float Tone::STRONG = 0.4; -Tone::Tone(int sample_rate, const float &freq, const float &l) : sample_rate_(sample_rate), freq_(freq), ampl_(ampl), sample_count_(0) -{ -} +Tone::Tone(int sample_rate, const float& freq, const float& ampl) : sample_rate_(sample_rate), freq_(freq), ampl_(ampl), sample_count_(0) {} -float Tone::get_sample(void) -{ - sample_count_++; - return ampl_ * sin(2 * M_PI * sample_count_ * freq_ / sample_rate_); +float Tone::get_sample(void) { + sample_count_++; + return ampl_ * sin(2 * M_PI * sample_count_ * freq_ / sample_rate_); } float Noise::WEAK = 0.05; float Noise::NORMAL = 0.2; float Noise::STRONG = 0.5; -Noise::Noise(const float &l) : ampl_(ampl) { - - // create a seeded generator - std::random_device r; - std::seed_seq s{r(), r(), r(), r(), r(), r(), r(), r()}; - generator = std::mt19937(s); +Noise::Noise(const float& ampl) : ampl_(ampl) { + // create a seeded generator + std::random_device r; + std::seed_seq s{r(), r(), r(), r(), r(), r(), r(), r()}; + generator = std::mt19937(s); - // centered at 0.0, standard deviation of 0.1 - distribution = normal_distribution (0.0, 0.1); + // centered at 0.0, standard deviation of 0.1 + distribution = normal_distribution(0.0, 0.1); } float Noise::get_sample(void) { return ampl_ * distribution(generator); } -GenerateSignal::GenerateSignal(int sample_rate) : sample_rate_(sample_rate) -{ -} +GenerateSignal::GenerateSignal(int sample_rate) : sample_rate_(sample_rate) {} -void GenerateSignal::add_tone(const float &freq, const float &l) { - tones_.push_back(Tone(sample_rate_, freq, ampl)); +void GenerateSignal::add_tone(const float& freq, const float& ampl) { + tones_.push_back(Tone(sample_rate_, freq, ampl)); } -void GenerateSignal::add_noise(const float &l) { - noises_.push_back(Noise(ampl)); +void GenerateSignal::add_noise(const float& ampl) { + noises_.push_back(Noise(ampl)); } float GenerateSignal::get_sample(void) { - float value = 0.0; + float value = 0.0; - for (vector::iterator tone = tones_.begin() ; tone != tones_.end(); ++tone) { - value += tone->get_sample(); - } + for (vector::iterator tone = tones_.begin(); tone != tones_.end(); ++tone) { + value += tone->get_sample(); + } - for (vector::iterator noise = noises_.begin() ; noise != noises_.end(); ++noise) { - value += noise->get_sample(); - } + for (vector::iterator noise = noises_.begin(); noise != noises_.end(); ++noise) { + value += noise->get_sample(); + } - return value; + return value; } +void GenerateSignal::write_file(const string& filepath, const float& seconds) { + FILE* fp = fopen(filepath.c_str(), "wb"); -void GenerateSignal::write_file(const string &filepath, const float &seconds) { - - FILE* fp = fopen(filepath.c_str(), "wb"); - - for (int i = 0 ; i < sample_rate_ * seconds; ++i) { - float sample = get_sample(); - fwrite(&sample, sizeof(float), 1, fp); - } - fclose(fp); + for (int i = 0; i < sample_rate_ * seconds; ++i) { + float sample = get_sample(); + fwrite(&sample, sizeof(float), 1, fp); + } + fclose(fp); } diff --git a/src/generate_signal.h b/src/generate_signal.h index d6e9aef..c644906 100644 --- a/src/generate_signal.h +++ b/src/generate_signal.h @@ -20,20 +20,20 @@ #ifndef _GENERATE_SIGNAL_H #define _GENERATE_SIGNAL_H -#include -#include #include +#include +#include class Tone { -public: + public: static float WEAK; static float NORMAL; static float STRONG; - Tone(int sample_rate, const float &freq, const float &l); + Tone(int sample_rate, const float& freq, const float& ampl); float get_sample(void); -private: + private: int sample_rate_; float freq_; float ampl_; @@ -41,32 +41,32 @@ class Tone { }; class Noise { -public: + public: static float WEAK; static float NORMAL; static float STRONG; - Noise(const float &l); + Noise(const float& ampl); float get_sample(void); -private: + private: float ampl_; std::mt19937 generator; std::normal_distribution distribution; }; class GenerateSignal { -public: + public: GenerateSignal(int sample_rate); - void add_tone(const float &freq, const float &l); - void add_noise(const float &l); + void add_tone(const float& freq, const float& ampl); + void add_noise(const float& ampl); float get_sample(void); - void write_file(const std::string &filepath, const float &seconds); + void write_file(const std::string& filepath, const float& seconds); -private: + private: int sample_rate_; std::vector tones_; std::vector noises_; diff --git a/src/helper_functions.cpp b/src/helper_functions.cpp index 21e8b86..e782b71 100644 --- a/src/helper_functions.cpp +++ b/src/helper_functions.cpp @@ -17,26 +17,26 @@ * along with this program. If not, see . */ -#include // size_t -#include // strerror -#include // struct stat, S_ISDIR +#include // struct stat, S_ISDIR +#include // size_t +#include // strerror -#include "logging.h" #include "helper_functions.h" +#include "logging.h" using namespace std; -bool dir_exists(const string &dir_path) { - struct stat st; - return (stat(dir_path.c_str(), &st) == 0 && S_ISDIR(st.st_mode)); +bool dir_exists(const string& dir_path) { + struct stat st; + return (stat(dir_path.c_str(), &st) == 0 && S_ISDIR(st.st_mode)); } -bool file_exists(const string &file_path) { - struct stat st; - return (stat(file_path.c_str(), &st) == 0 && S_ISREG(st.st_mode)); +bool file_exists(const string& file_path) { + struct stat st; + return (stat(file_path.c_str(), &st) == 0 && S_ISREG(st.st_mode)); } -bool make_dir(const string &dir_path) { +bool make_dir(const string& dir_path) { if (dir_exists(dir_path)) { return true; } @@ -48,8 +48,7 @@ bool make_dir(const string &dir_path) { return true; } -bool make_subdirs(const string &basedir, const string &subdirs) { - +bool make_subdirs(const string& basedir, const string& subdirs) { // if final directory exists then nothing to do const string delim = "/"; const string final_path = basedir + delim + subdirs; @@ -64,15 +63,14 @@ bool make_subdirs(const string &basedir, const string &subdirs) { if (!make_dir(basedir + delim + subdirs.substr(0, index))) { return false; } - index = subdirs.find_first_of(delim, index+1); + index = subdirs.find_first_of(delim, index + 1); } make_dir(final_path); return dir_exists(final_path); } -string make_dated_subdirs(const string &basedir, const struct tm *time) { - +string make_dated_subdirs(const string& basedir, const struct tm* time) { // use the time to build the date subdirectories char date_path[11]; strftime(date_path, sizeof(date_path), "%Y/%m/%d", time); diff --git a/src/helper_functions.h b/src/helper_functions.h index 728fd9e..9db4b08 100644 --- a/src/helper_functions.h +++ b/src/helper_functions.h @@ -20,13 +20,13 @@ #ifndef _HELPER_FUNCTIONS_H #define _HELPER_FUNCTIONS_H -#include // struct tm +#include // struct tm #include -bool dir_exists(const std::string &dir_path); -bool file_exists(const std::string &file_path); -bool make_dir(const std::string &dir_path); -bool make_subdirs(const std::string &basedir, const std::string &subdirs); -std::string make_dated_subdirs(const std::string &basedir, const struct tm *time); +bool dir_exists(const std::string& dir_path); +bool file_exists(const std::string& file_path); +bool make_dir(const std::string& dir_path); +bool make_subdirs(const std::string& basedir, const std::string& subdirs); +std::string make_dated_subdirs(const std::string& basedir, const struct tm* time); #endif /* _HELPER_FUNCTIONS_H */ diff --git a/src/input-common.cpp b/src/input-common.cpp index 157fc3b..20255fc 100644 --- a/src/input-common.cpp +++ b/src/input-common.cpp @@ -18,116 +18,114 @@ * along with this program. If not, see . */ - -#include +#include "input-common.h" #include -#include // dlopen, dlsym +#include // dlopen, dlsym #include #include -#include // asprintf -#include // free +#include // asprintf +#include // free #include -#include "input-common.h" +#include using namespace std; -typedef input_t *(*input_new_func_t)(void); +typedef input_t* (*input_new_func_t)(void); -input_t *input_new(char const * const type) { - assert(type != NULL); - void *dlhandle = dlopen(NULL, RTLD_NOW); - assert(dlhandle != NULL); - char *fname = NULL; - int chars_written = asprintf(&fname, "%s_input_new", type); - if(chars_written <= 0) { - return NULL; - } - input_new_func_t fptr = (input_new_func_t)dlsym(dlhandle, fname); - free(fname); - if(fptr == NULL) { - return NULL; - } - input_t *input = (*fptr)(); - assert(input->init != NULL); - assert(input->run_rx_thread != NULL); - assert(input->set_centerfreq != NULL); - return input; +input_t* input_new(char const* const type) { + assert(type != NULL); + void* dlhandle = dlopen(NULL, RTLD_NOW); + assert(dlhandle != NULL); + char* fname = NULL; + int chars_written = asprintf(&fname, "%s_input_new", type); + if (chars_written <= 0) { + return NULL; + } + input_new_func_t fptr = (input_new_func_t)dlsym(dlhandle, fname); + free(fname); + if (fptr == NULL) { + return NULL; + } + input_t* input = (*fptr)(); + assert(input->init != NULL); + assert(input->run_rx_thread != NULL); + assert(input->set_centerfreq != NULL); + return input; } -int input_init(input_t * const input) { - assert(input != NULL); - input_state_t new_state = INPUT_FAILED; // fail-safe default - errno = 0; - int ret = input->init(input); - if(ret < 0) { - ret = -1; - } else if((ret = pthread_mutex_init(&input->buffer_lock, NULL)) != 0) { - errno = ret; - ret = -1; - } else { - new_state = INPUT_INITIALIZED; - ret = 0; - } - input->state = new_state; - return ret; +int input_init(input_t* const input) { + assert(input != NULL); + input_state_t new_state = INPUT_FAILED; // fail-safe default + errno = 0; + int ret = input->init(input); + if (ret < 0) { + ret = -1; + } else if ((ret = pthread_mutex_init(&input->buffer_lock, NULL)) != 0) { + errno = ret; + ret = -1; + } else { + new_state = INPUT_INITIALIZED; + ret = 0; + } + input->state = new_state; + return ret; } -int input_start(input_t * const input) { - assert(input != NULL); - assert(input->dev_data != NULL); - assert(input->state == INPUT_INITIALIZED); - int err = pthread_create(&input->rx_thread, NULL, input->run_rx_thread, (void *)input); - if(err != 0) { - errno = err; - return -1; - } - return 0; +int input_start(input_t* const input) { + assert(input != NULL); + assert(input->dev_data != NULL); + assert(input->state == INPUT_INITIALIZED); + int err = pthread_create(&input->rx_thread, NULL, input->run_rx_thread, (void*)input); + if (err != 0) { + errno = err; + return -1; + } + return 0; } -int input_parse_config(input_t * const input, libconfig::Setting &cfg) { - assert(input != NULL); - if(input->parse_config != NULL) { - return input->parse_config(input, cfg); - } else { -// Very simple inputs (like stdin) might not necessarily have any configuration -// variables, so it's legal not to have parse_config defined. - return 0; - } +int input_parse_config(input_t* const input, libconfig::Setting& cfg) { + assert(input != NULL); + if (input->parse_config != NULL) { + return input->parse_config(input, cfg); + } else { + // Very simple inputs (like stdin) might not necessarily have any configuration + // variables, so it's legal not to have parse_config defined. + return 0; + } } -int input_stop(input_t * const input) { - assert(input != NULL); - assert(input->dev_data != NULL); - int err = 0; - errno = 0; - if(input->state == INPUT_RUNNING && input->stop != NULL) { - err = input->stop(input); - if(err != 0) { - input->state = INPUT_FAILED; - return -1; - } - } - input->state = INPUT_STOPPED; - err = pthread_join(input->rx_thread, NULL); - if(err != 0) { - errno = err; - return -1; - } - return 0; +int input_stop(input_t* const input) { + assert(input != NULL); + assert(input->dev_data != NULL); + int err = 0; + errno = 0; + if (input->state == INPUT_RUNNING && input->stop != NULL) { + err = input->stop(input); + if (err != 0) { + input->state = INPUT_FAILED; + return -1; + } + } + input->state = INPUT_STOPPED; + err = pthread_join(input->rx_thread, NULL); + if (err != 0) { + errno = err; + return -1; + } + return 0; } -int input_set_centerfreq(input_t * const input, int const centerfreq) { - assert(input != NULL); - assert(input->dev_data != NULL); - if(input->state != INPUT_RUNNING) { - return -1; - } - int ret = input->set_centerfreq(input, centerfreq); - if(ret != 0) { - input->state = INPUT_FAILED; - return -1; - } - input->centerfreq = centerfreq; - return 0; +int input_set_centerfreq(input_t* const input, int const centerfreq) { + assert(input != NULL); + assert(input->dev_data != NULL); + if (input->state != INPUT_RUNNING) { + return -1; + } + int ret = input->set_centerfreq(input, centerfreq); + if (ret != 0) { + input->state = INPUT_FAILED; + return -1; + } + input->centerfreq = centerfreq; + return 0; } - diff --git a/src/input-common.h b/src/input-common.h index 18bc55b..ccf926a 100644 --- a/src/input-common.h +++ b/src/input-common.h @@ -28,52 +28,39 @@ #define MODULE_EXPORT extern "C" #endif /* __GNUC__ */ -typedef enum { - SFMT_UNDEF = 0, - SFMT_U8, - SFMT_S8, - SFMT_S16, - SFMT_F32 -} sample_format_t; +typedef enum { SFMT_UNDEF = 0, SFMT_U8, SFMT_S8, SFMT_S16, SFMT_F32 } sample_format_t; #define SAMPLE_FORMAT_CNT 5 -typedef enum { - INPUT_UNKNOWN = 0, - INPUT_INITIALIZED, - INPUT_RUNNING, - INPUT_FAILED, - INPUT_STOPPED, - INPUT_DISABLED -} input_state_t; +typedef enum { INPUT_UNKNOWN = 0, INPUT_INITIALIZED, INPUT_RUNNING, INPUT_FAILED, INPUT_STOPPED, INPUT_DISABLED } input_state_t; #define INPUT_STATE_CNT 6 typedef struct input_t input_t; struct input_t { - unsigned char *buffer; - void *dev_data; - size_t buf_size, bufs, bufe; - size_t overflow_count; - input_state_t state; - sample_format_t sfmt; - float fullscale; - int bytes_per_sample; - int sample_rate; - int centerfreq; - int (*parse_config)(input_t * const input, libconfig::Setting &cfg); - int (*init)(input_t * const input); - void *(*run_rx_thread)(void *input_ptr); // to be launched via pthread_create() - int (*set_centerfreq)(input_t * const input, int const centerfreq); - int (*stop)(input_t * const input) ; - pthread_t rx_thread; - pthread_mutex_t buffer_lock; + unsigned char* buffer; + void* dev_data; + size_t buf_size, bufs, bufe; + size_t overflow_count; + input_state_t state; + sample_format_t sfmt; + float fullscale; + int bytes_per_sample; + int sample_rate; + int centerfreq; + int (*parse_config)(input_t* const input, libconfig::Setting& cfg); + int (*init)(input_t* const input); + void* (*run_rx_thread)(void* input_ptr); // to be launched via pthread_create() + int (*set_centerfreq)(input_t* const input, int const centerfreq); + int (*stop)(input_t* const input); + pthread_t rx_thread; + pthread_mutex_t buffer_lock; }; -input_t *input_new(char const * const type); -int input_init(input_t * const input); -int input_parse_config(input_t * const input, libconfig::Setting &cfg); -int input_start(input_t * const input); -int input_set_centerfreq(input_t * const input, int const centerfreq); -int input_stop(input_t * const input); +input_t* input_new(char const* const type); +int input_init(input_t* const input); +int input_parse_config(input_t* const input, libconfig::Setting& cfg); +int input_start(input_t* const input); +int input_set_centerfreq(input_t* const input, int const centerfreq); +int input_stop(input_t* const input); #endif /* _INPUT_COMMON_H */ diff --git a/src/input-file.cpp b/src/input-file.cpp index f9f7516..9277b56 100644 --- a/src/input-file.cpp +++ b/src/input-file.cpp @@ -18,167 +18,166 @@ * along with this program. If not, see . */ -#include +#include "input-file.h" // file_dev_data_t #include -#include // SCHAR_MAX +#include // SCHAR_MAX +#include #include -#include // FIXME: get rid of this -#include // usleep -#include // Setting -#include "input-common.h" // input_t, sample_format_t, input_state_t, MODULE_EXPORT -#include "input-helpers.h" // circbuffer_append -#include "input-file.h" // file_dev_data_t -#include "rtl_airband.h" // do_exit, fft_size, debug_print, XCALLOC, error() +#include // FIXME: get rid of this +#include // usleep +#include // Setting +#include "input-common.h" // input_t, sample_format_t, input_state_t, MODULE_EXPORT +#include "input-helpers.h" // circbuffer_append +#include "rtl_airband.h" // do_exit, fft_size, debug_print, XCALLOC, error() using namespace std; -int file_parse_config(input_t * const input, libconfig::Setting &cfg) { - assert(input != NULL); - file_dev_data_t *dev_data = (file_dev_data_t *)input->dev_data; - assert(dev_data != NULL); - - if(cfg.exists("filepath")) { - dev_data->filepath = strdup(cfg["filepath"]); - } else { - cerr << "File configuration error: no 'filepath' given\n"; - error(); - } - - if (cfg.exists("speedup_factor")) { - if (cfg["speedup_factor"].getType() == libconfig::Setting::TypeInt) { - dev_data->speedup_factor = (int)cfg["speedup_factor"]; - } else if (cfg["speedup_factor"].getType() == libconfig::Setting::TypeFloat) { - dev_data->speedup_factor = (float)cfg["speedup_factor"]; - } else { - cerr << "File configuration error: 'speedup_factor' must be a float or int if set\n"; - error(); - } - if (dev_data->speedup_factor <= 0.0) { - cerr << "File configuration error: 'speedup_factor' must be >= 0.0\n"; - error(); - } - } else { - dev_data->speedup_factor = 4; - } - - return 0; +int file_parse_config(input_t* const input, libconfig::Setting& cfg) { + assert(input != NULL); + file_dev_data_t* dev_data = (file_dev_data_t*)input->dev_data; + assert(dev_data != NULL); + + if (cfg.exists("filepath")) { + dev_data->filepath = strdup(cfg["filepath"]); + } else { + cerr << "File configuration error: no 'filepath' given\n"; + error(); + } + + if (cfg.exists("speedup_factor")) { + if (cfg["speedup_factor"].getType() == libconfig::Setting::TypeInt) { + dev_data->speedup_factor = (int)cfg["speedup_factor"]; + } else if (cfg["speedup_factor"].getType() == libconfig::Setting::TypeFloat) { + dev_data->speedup_factor = (float)cfg["speedup_factor"]; + } else { + cerr << "File configuration error: 'speedup_factor' must be a float or int if set\n"; + error(); + } + if (dev_data->speedup_factor <= 0.0) { + cerr << "File configuration error: 'speedup_factor' must be >= 0.0\n"; + error(); + } + } else { + dev_data->speedup_factor = 4; + } + + return 0; } -int file_init(input_t * const input) { - assert(input != NULL); - file_dev_data_t *dev_data = (file_dev_data_t *)input->dev_data; - assert(dev_data != NULL); +int file_init(input_t* const input) { + assert(input != NULL); + file_dev_data_t* dev_data = (file_dev_data_t*)input->dev_data; + assert(dev_data != NULL); - dev_data->input_file = fopen(dev_data->filepath, "rb"); - if(!dev_data->input_file) { - cerr << "File input failed to open '" << dev_data->filepath << "' - " << strerror(errno) << endl; - error(); - } + dev_data->input_file = fopen(dev_data->filepath, "rb"); + if (!dev_data->input_file) { + cerr << "File input failed to open '" << dev_data->filepath << "' - " << strerror(errno) << endl; + error(); + } - log(LOG_INFO, "File input %s initialized\n", dev_data->filepath); - return 0; + log(LOG_INFO, "File input %s initialized\n", dev_data->filepath); + return 0; } -void *file_rx_thread(void *ctx) { - input_t *input = (input_t *)ctx; - assert(input != NULL); - assert(input->sample_rate != 0); - file_dev_data_t *dev_data = (file_dev_data_t *)input->dev_data; - assert(dev_data != NULL); - assert(dev_data->input_file != NULL); - assert(dev_data->speedup_factor != 0.0); - - size_t buf_len = (input->buf_size/2) - 1; - unsigned char *buf = (unsigned char *)XCALLOC(1, buf_len); - - float time_per_byte_ms = 1000 / (input->sample_rate * input->bytes_per_sample * 2 * dev_data->speedup_factor); - - log(LOG_DEBUG, "sample_rate: %d, bytes_per_sample: %d, speedup_factor: %f, time_per_byte_ms: %f\n", - input->sample_rate, input->bytes_per_sample, dev_data->speedup_factor, time_per_byte_ms); - - input->state = INPUT_RUNNING; - - while(true) { - if(do_exit) { - break; - } - if(feof(dev_data->input_file)) { - log(LOG_INFO, "File '%s': hit end of file at %d, disabling\n", dev_data->filepath, ftell(dev_data->input_file)); - input->state = INPUT_FAILED; - break; - } - if(ferror(dev_data->input_file)) { - log(LOG_ERR, "File '%s': read error (%d), disabling\n", dev_data->filepath, ferror(dev_data->input_file)); - input->state = INPUT_FAILED; - break; - } - - timeval start; - gettimeofday(&start, NULL); - - size_t space_left; - pthread_mutex_lock(&input->buffer_lock); - if (input->bufe >= input->bufs) { - space_left = input->bufs + (input->buf_size - input->bufe); - } else { - space_left = input->bufs - input->bufe; - } - pthread_mutex_unlock(&input->buffer_lock); - - if (space_left > buf_len) { - size_t len = fread(buf, sizeof(unsigned char), buf_len, dev_data->input_file); - circbuffer_append(input, buf, len); - - timeval end; - gettimeofday(&end, NULL); - - int time_taken_ms = delta_sec(&start, &end) * 1000; - int sleep_time_ms = len * time_per_byte_ms - time_taken_ms; - - if(sleep_time_ms > 0) { - SLEEP(sleep_time_ms); - } - } else { - SLEEP(10); - } - } - - free(buf); - return 0; +void* file_rx_thread(void* ctx) { + input_t* input = (input_t*)ctx; + assert(input != NULL); + assert(input->sample_rate != 0); + file_dev_data_t* dev_data = (file_dev_data_t*)input->dev_data; + assert(dev_data != NULL); + assert(dev_data->input_file != NULL); + assert(dev_data->speedup_factor != 0.0); + + size_t buf_len = (input->buf_size / 2) - 1; + unsigned char* buf = (unsigned char*)XCALLOC(1, buf_len); + + float time_per_byte_ms = 1000 / (input->sample_rate * input->bytes_per_sample * 2 * dev_data->speedup_factor); + + log(LOG_DEBUG, "sample_rate: %d, bytes_per_sample: %d, speedup_factor: %f, time_per_byte_ms: %f\n", input->sample_rate, input->bytes_per_sample, dev_data->speedup_factor, time_per_byte_ms); + + input->state = INPUT_RUNNING; + + while (true) { + if (do_exit) { + break; + } + if (feof(dev_data->input_file)) { + log(LOG_INFO, "File '%s': hit end of file at %d, disabling\n", dev_data->filepath, ftell(dev_data->input_file)); + input->state = INPUT_FAILED; + break; + } + if (ferror(dev_data->input_file)) { + log(LOG_ERR, "File '%s': read error (%d), disabling\n", dev_data->filepath, ferror(dev_data->input_file)); + input->state = INPUT_FAILED; + break; + } + + timeval start; + gettimeofday(&start, NULL); + + size_t space_left; + pthread_mutex_lock(&input->buffer_lock); + if (input->bufe >= input->bufs) { + space_left = input->bufs + (input->buf_size - input->bufe); + } else { + space_left = input->bufs - input->bufe; + } + pthread_mutex_unlock(&input->buffer_lock); + + if (space_left > buf_len) { + size_t len = fread(buf, sizeof(unsigned char), buf_len, dev_data->input_file); + circbuffer_append(input, buf, len); + + timeval end; + gettimeofday(&end, NULL); + + int time_taken_ms = delta_sec(&start, &end) * 1000; + int sleep_time_ms = len * time_per_byte_ms - time_taken_ms; + + if (sleep_time_ms > 0) { + SLEEP(sleep_time_ms); + } + } else { + SLEEP(10); + } + } + + free(buf); + return 0; } -int file_set_centerfreq(input_t * const /*input*/, int const /*centerfreq*/) { - return 0; +int file_set_centerfreq(input_t* const /*input*/, int const /*centerfreq*/) { + return 0; } -int file_stop(input_t * const input) { - assert(input != NULL); - file_dev_data_t *dev_data = (file_dev_data_t *)input->dev_data; - assert(dev_data != NULL); - fclose(dev_data->input_file); - dev_data->input_file = NULL; - return 0; +int file_stop(input_t* const input) { + assert(input != NULL); + file_dev_data_t* dev_data = (file_dev_data_t*)input->dev_data; + assert(dev_data != NULL); + fclose(dev_data->input_file); + dev_data->input_file = NULL; + return 0; } -MODULE_EXPORT input_t *file_input_new() { - file_dev_data_t *dev_data = (file_dev_data_t *)XCALLOC(1, sizeof(file_dev_data_t)); - dev_data->input_file = NULL; - dev_data->speedup_factor = 0.0; - - input_t *input = (input_t *)XCALLOC(1, sizeof(input_t)); - input->dev_data = dev_data; - input->state = INPUT_UNKNOWN; - input->sfmt = SFMT_U8; - input->fullscale = (float)SCHAR_MAX - 0.5f; - input->bytes_per_sample = sizeof(unsigned char); - input->sample_rate = 0; - input->parse_config = &file_parse_config; - input->init = &file_init; - input->run_rx_thread = &file_rx_thread; - input->set_centerfreq = &file_set_centerfreq; - input->stop = &file_stop; - - return input; +MODULE_EXPORT input_t* file_input_new() { + file_dev_data_t* dev_data = (file_dev_data_t*)XCALLOC(1, sizeof(file_dev_data_t)); + dev_data->input_file = NULL; + dev_data->speedup_factor = 0.0; + + input_t* input = (input_t*)XCALLOC(1, sizeof(input_t)); + input->dev_data = dev_data; + input->state = INPUT_UNKNOWN; + input->sfmt = SFMT_U8; + input->fullscale = (float)SCHAR_MAX - 0.5f; + input->bytes_per_sample = sizeof(unsigned char); + input->sample_rate = 0; + input->parse_config = &file_parse_config; + input->init = &file_init; + input->run_rx_thread = &file_rx_thread; + input->set_centerfreq = &file_set_centerfreq; + input->stop = &file_stop; + + return input; } // vim: ts=4 diff --git a/src/input-file.h b/src/input-file.h index c0ed8cc..3d0b033 100644 --- a/src/input-file.h +++ b/src/input-file.h @@ -18,14 +18,14 @@ * along with this program. If not, see . */ -#include -#include #include +#include +#include typedef struct { - char *filepath; - FILE *input_file; - float speedup_factor; + char* filepath; + FILE* input_file; + float speedup_factor; } file_dev_data_t; // vim: ts=4 diff --git a/src/input-helpers.cpp b/src/input-helpers.cpp index 0b0757e..dda3eb7 100644 --- a/src/input-helpers.cpp +++ b/src/input-helpers.cpp @@ -18,11 +18,11 @@ * along with this program. If not, see . */ -#include // cerr -#include // memcpy -#include // pthread_mutex_lock, unlock -#include "input-common.h" // input_t -#include "rtl_airband.h" // debug_print +#include // pthread_mutex_lock, unlock +#include // memcpy +#include // cerr +#include "input-common.h" // input_t +#include "rtl_airband.h" // debug_print /* Write input data into circular buffer input->buffer. * In general, input->buffer_size is not an exact multiple of len, @@ -34,35 +34,32 @@ * so that the signal windowing function could handle the whole FFT batch * without wrapping. */ -void circbuffer_append(input_t * const input, unsigned char *buf, size_t len) { - if(len == 0) return; - pthread_mutex_lock(&input->buffer_lock); - size_t space_left = input->buf_size - input->bufe; - if(space_left >= len) { - memcpy(input->buffer + input->bufe, buf, len); - if(input->bufe == 0) { - memcpy(input->buffer + input->buf_size, input->buffer, - std::min(len, 2 * input->bytes_per_sample * fft_size)); - debug_print("tail_len=%zu bytes\n", - std::min(len, 2 * input->bytes_per_sample * fft_size)); - } - } else { - memcpy(input->buffer + input->bufe, buf, space_left); - memcpy(input->buffer, buf + space_left, len - space_left); - memcpy(input->buffer + input->buf_size, input->buffer, - std::min(len - space_left, 2 * input->bytes_per_sample * fft_size)); - debug_print("buf wrap: space_left=%zu len=%zu bufe=%zu wrap_len=%zu tail_len=%zu\n", - space_left, len, input->bufe, len - space_left, - std::min(len - space_left, 2 * input->bytes_per_sample * fft_size)); - } +void circbuffer_append(input_t* const input, unsigned char* buf, size_t len) { + if (len == 0) + return; + pthread_mutex_lock(&input->buffer_lock); + size_t space_left = input->buf_size - input->bufe; + if (space_left >= len) { + memcpy(input->buffer + input->bufe, buf, len); + if (input->bufe == 0) { + memcpy(input->buffer + input->buf_size, input->buffer, std::min(len, 2 * input->bytes_per_sample * fft_size)); + debug_print("tail_len=%zu bytes\n", std::min(len, 2 * input->bytes_per_sample * fft_size)); + } + } else { + memcpy(input->buffer + input->bufe, buf, space_left); + memcpy(input->buffer, buf + space_left, len - space_left); + memcpy(input->buffer + input->buf_size, input->buffer, std::min(len - space_left, 2 * input->bytes_per_sample * fft_size)); + debug_print("buf wrap: space_left=%zu len=%zu bufe=%zu wrap_len=%zu tail_len=%zu\n", space_left, len, input->bufe, len - space_left, + std::min(len - space_left, 2 * input->bytes_per_sample * fft_size)); + } - size_t old_end = input->bufe; - input->bufe = (input->bufe + len) % input->buf_size; - if(old_end < input->bufs && input->bufe >= input->bufs) { - std::cerr << "Warning: buffer overflow\n"; - input->overflow_count++; - } - pthread_mutex_unlock(&input->buffer_lock); + size_t old_end = input->bufe; + input->bufe = (input->bufe + len) % input->buf_size; + if (old_end < input->bufs && input->bufe >= input->bufs) { + std::cerr << "Warning: buffer overflow\n"; + input->overflow_count++; + } + pthread_mutex_unlock(&input->buffer_lock); } // vim: ts=4 diff --git a/src/input-helpers.h b/src/input-helpers.h index aa07769..966d734 100644 --- a/src/input-helpers.h +++ b/src/input-helpers.h @@ -18,7 +18,7 @@ * along with this program. If not, see . */ -#include "input-common.h" // input_t +#include "input-common.h" // input_t // input-helpers.cpp -void circbuffer_append(input_t * const input, unsigned char *buf, size_t len); +void circbuffer_append(input_t* const input, unsigned char* buf, size_t len); diff --git a/src/input-mirisdr.cpp b/src/input-mirisdr.cpp index e1c0a0e..99f171f 100644 --- a/src/input-mirisdr.cpp +++ b/src/input-mirisdr.cpp @@ -18,224 +18,222 @@ * along with this program. If not, see . */ +#include "input-mirisdr.h" // mirisdr_dev_data_t #include -#include -#include // SCHAR_MAX +#include // SCHAR_MAX +#include +#include // uint32_t #include -#include // uint32_t #include #include -#include // FIXME: get rid of this -#include // Setting -#include -#include "input-common.h" // input_t, sample_format_t, input_state_t, MODULE_EXPORT -#include "input-helpers.h" // circbuffer_append -#include "input-mirisdr.h" // mirisdr_dev_data_t -#include "rtl_airband.h" // do_exit, fft_size, debug_print, XCALLOC, error() +#include // FIXME: get rid of this +#include +#include // Setting +#include "input-common.h" // input_t, sample_format_t, input_state_t, MODULE_EXPORT +#include "input-helpers.h" // circbuffer_append +#include "rtl_airband.h" // do_exit, fft_size, debug_print, XCALLOC, error() using namespace std; -static void mirisdr_callback(unsigned char *buf, uint32_t len, void *ctx) { - if(do_exit) return; - input_t *input = (input_t *)ctx; - circbuffer_append(input, buf, (size_t)len); +static void mirisdr_callback(unsigned char* buf, uint32_t len, void* ctx) { + if (do_exit) + return; + input_t* input = (input_t*)ctx; + circbuffer_append(input, buf, (size_t)len); } /* based on librtlsdr-keenerd, (c) Kyle Keen */ -static bool mirisdr_nearest_gain(mirisdr_dev_t *dev, int target_gain, int *nearest) { - assert(nearest != NULL); - int i, r, err1, err2, count; - int *gains; - r = mirisdr_set_tuner_gain_mode(dev, 1); - if (r < 0) { - return false; - } - count = mirisdr_get_tuner_gains(dev, NULL); - if (count <= 0) { - return false; - } - gains = (int *)XCALLOC(count, sizeof(int)); - count = mirisdr_get_tuner_gains(dev, gains); - *nearest = gains[0]; - for (i = 0; i < count; i++) { - err1 = abs(target_gain - *nearest); - err2 = abs(target_gain - gains[i]); - if (err2 < err1) { - *nearest = gains[i]; - } - } - free(gains); - return true; +static bool mirisdr_nearest_gain(mirisdr_dev_t* dev, int target_gain, int* nearest) { + assert(nearest != NULL); + int i, r, err1, err2, count; + int* gains; + r = mirisdr_set_tuner_gain_mode(dev, 1); + if (r < 0) { + return false; + } + count = mirisdr_get_tuner_gains(dev, NULL); + if (count <= 0) { + return false; + } + gains = (int*)XCALLOC(count, sizeof(int)); + count = mirisdr_get_tuner_gains(dev, gains); + *nearest = gains[0]; + for (i = 0; i < count; i++) { + err1 = abs(target_gain - *nearest); + err2 = abs(target_gain - gains[i]); + if (err2 < err1) { + *nearest = gains[i]; + } + } + free(gains); + return true; } -static int mirisdr_find_device_by_serial(char const * const s) { - char vendor[256] = {0}, product[256] = {0}, serial[256] = {0}; - int count = mirisdr_get_device_count(); - if(count < 1) { - return -1; - } - for(int i = 0; i < count; i++) { - mirisdr_get_device_usb_strings(i, vendor, product, serial); - if (strcmp(s, serial) != 0) { - continue; - } - return i; - } - return -1; +static int mirisdr_find_device_by_serial(char const* const s) { + char vendor[256] = {0}, product[256] = {0}, serial[256] = {0}; + int count = mirisdr_get_device_count(); + if (count < 1) { + return -1; + } + for (int i = 0; i < count; i++) { + mirisdr_get_device_usb_strings(i, vendor, product, serial); + if (strcmp(s, serial) != 0) { + continue; + } + return i; + } + return -1; } -int mirisdr_init(input_t * const input) { - mirisdr_dev_data_t *dev_data = (mirisdr_dev_data_t *)input->dev_data; - if(dev_data->serial != NULL) { - dev_data->index = mirisdr_find_device_by_serial(dev_data->serial); - if(dev_data->index < 0) { - cerr<<"MiriSDR device with serial number "<serial<<" not found\n"; - error(); - } - } - - dev_data->dev = NULL; - mirisdr_open(&dev_data->dev, dev_data->index); - if(NULL == dev_data->dev) { - log(LOG_ERR, "Failed to open mirisdr device #%d.\n", dev_data->index); - error(); - } - - char transfer_str[] = "BULK"; - char sample_format_str[] = "504_S8"; - - mirisdr_dev_t *miri = dev_data->dev; - int r = mirisdr_set_transfer(miri, transfer_str); - if (r < 0) { - log(LOG_ERR, "Failed to set bulk transfer mode for MiriSDR device #%d: error %d\n", dev_data->index, r); - error(); - } - r = mirisdr_set_sample_rate(miri, input->sample_rate); - if (r < 0) { - log(LOG_ERR, "Failed to set sample rate for device #%d. Error %d.\n", dev_data->index, r); - } - - r = mirisdr_set_center_freq(miri, input->centerfreq - dev_data->correction); - if(r < 0) { - log(LOG_ERR, "Failed to set center freq for device #%d. Error %d.\n", dev_data->index, r); - } - - int ngain = 0; - if(mirisdr_nearest_gain(miri, dev_data->gain, &ngain) != true) { - log(LOG_ERR, "Failed to read supported gain list for device #%d\n", dev_data->index); - error(); - } - r = mirisdr_set_tuner_gain_mode(miri, 1); - r |= mirisdr_set_tuner_gain(miri, ngain); - if (r < 0) { - log(LOG_ERR, "Failed to set gain to %d for device #%d: error %d\n", - ngain, dev_data->index, r); - } else { - log(LOG_INFO, "Device #%d: gain set to %d dB\n", dev_data->index, - mirisdr_get_tuner_gain(miri)); - } - r = mirisdr_set_sample_format(miri, sample_format_str); - if (r < 0) { - log(LOG_ERR, "Failed to set sample format for device #%d: error %d\n", dev_data->index, r); - error(); - } - mirisdr_reset_buffer(miri); - log(LOG_INFO, "MiriSDR device %d initialized\n", dev_data->index); - return 0; +int mirisdr_init(input_t* const input) { + mirisdr_dev_data_t* dev_data = (mirisdr_dev_data_t*)input->dev_data; + if (dev_data->serial != NULL) { + dev_data->index = mirisdr_find_device_by_serial(dev_data->serial); + if (dev_data->index < 0) { + cerr << "MiriSDR device with serial number " << dev_data->serial << " not found\n"; + error(); + } + } + + dev_data->dev = NULL; + mirisdr_open(&dev_data->dev, dev_data->index); + if (NULL == dev_data->dev) { + log(LOG_ERR, "Failed to open mirisdr device #%d.\n", dev_data->index); + error(); + } + + char transfer_str[] = "BULK"; + char sample_format_str[] = "504_S8"; + + mirisdr_dev_t* miri = dev_data->dev; + int r = mirisdr_set_transfer(miri, transfer_str); + if (r < 0) { + log(LOG_ERR, "Failed to set bulk transfer mode for MiriSDR device #%d: error %d\n", dev_data->index, r); + error(); + } + r = mirisdr_set_sample_rate(miri, input->sample_rate); + if (r < 0) { + log(LOG_ERR, "Failed to set sample rate for device #%d. Error %d.\n", dev_data->index, r); + } + + r = mirisdr_set_center_freq(miri, input->centerfreq - dev_data->correction); + if (r < 0) { + log(LOG_ERR, "Failed to set center freq for device #%d. Error %d.\n", dev_data->index, r); + } + + int ngain = 0; + if (mirisdr_nearest_gain(miri, dev_data->gain, &ngain) != true) { + log(LOG_ERR, "Failed to read supported gain list for device #%d\n", dev_data->index); + error(); + } + r = mirisdr_set_tuner_gain_mode(miri, 1); + r |= mirisdr_set_tuner_gain(miri, ngain); + if (r < 0) { + log(LOG_ERR, "Failed to set gain to %d for device #%d: error %d\n", ngain, dev_data->index, r); + } else { + log(LOG_INFO, "Device #%d: gain set to %d dB\n", dev_data->index, mirisdr_get_tuner_gain(miri)); + } + r = mirisdr_set_sample_format(miri, sample_format_str); + if (r < 0) { + log(LOG_ERR, "Failed to set sample format for device #%d: error %d\n", dev_data->index, r); + error(); + } + mirisdr_reset_buffer(miri); + log(LOG_INFO, "MiriSDR device %d initialized\n", dev_data->index); + return 0; } -void *mirisdr_rx_thread(void *ctx) { - input_t *input = (input_t *)ctx; - mirisdr_dev_data_t *dev_data = (mirisdr_dev_data_t *)input->dev_data; - assert(dev_data->dev != NULL); - - input->state = INPUT_RUNNING; - if(mirisdr_read_async(dev_data->dev, mirisdr_callback, ctx, dev_data->bufcnt, MIRISDR_BUFSIZE) < 0) { - log(LOG_ERR, "MiriSDR device #%d: async read failed, disabling\n", dev_data->index); - input->state = INPUT_FAILED; - } - return 0; +void* mirisdr_rx_thread(void* ctx) { + input_t* input = (input_t*)ctx; + mirisdr_dev_data_t* dev_data = (mirisdr_dev_data_t*)input->dev_data; + assert(dev_data->dev != NULL); + + input->state = INPUT_RUNNING; + if (mirisdr_read_async(dev_data->dev, mirisdr_callback, ctx, dev_data->bufcnt, MIRISDR_BUFSIZE) < 0) { + log(LOG_ERR, "MiriSDR device #%d: async read failed, disabling\n", dev_data->index); + input->state = INPUT_FAILED; + } + return 0; } -int mirisdr_stop(input_t * const input) { - mirisdr_dev_data_t *dev_data = (mirisdr_dev_data_t *)input->dev_data; - assert(dev_data->dev != NULL); +int mirisdr_stop(input_t* const input) { + mirisdr_dev_data_t* dev_data = (mirisdr_dev_data_t*)input->dev_data; + assert(dev_data->dev != NULL); - if(mirisdr_cancel_async(dev_data->dev) < 0) { - return -1; - } - return 0; + if (mirisdr_cancel_async(dev_data->dev) < 0) { + return -1; + } + return 0; } -int mirisdr_set_centerfreq(input_t * const input, int const centerfreq) { - mirisdr_dev_data_t *dev_data = (mirisdr_dev_data_t *)input->dev_data; - assert(dev_data->dev != NULL); - - int r = mirisdr_set_center_freq(dev_data->dev, centerfreq - dev_data->correction); - if(r < 0) { - log(LOG_ERR, "Failed to set centerfreq for MiriSDR device #%d: error %d\n", - dev_data->index, r); - return -1; - } - return 0; +int mirisdr_set_centerfreq(input_t* const input, int const centerfreq) { + mirisdr_dev_data_t* dev_data = (mirisdr_dev_data_t*)input->dev_data; + assert(dev_data->dev != NULL); + + int r = mirisdr_set_center_freq(dev_data->dev, centerfreq - dev_data->correction); + if (r < 0) { + log(LOG_ERR, "Failed to set centerfreq for MiriSDR device #%d: error %d\n", dev_data->index, r); + return -1; + } + return 0; } -int mirisdr_parse_config(input_t * const input, libconfig::Setting &cfg) { - mirisdr_dev_data_t *dev_data = (mirisdr_dev_data_t *)input->dev_data; - if(cfg.exists("serial")) { - dev_data->serial = strdup(cfg["serial"]); - } else if(cfg.exists("index")) { - dev_data->index = (int)cfg["index"]; - } else { - cerr<<"MiriSDR configuration error: no index and no serial number given\n"; - error(); - } - if(cfg.exists("gain")) { - dev_data->gain = (int)cfg["gain"]; - } else { - cerr<<"MiriSDR configuration error: gain is not configured\n"; - error(); - } - if(cfg.exists("correction")) { - dev_data->correction = (int)cfg["correction"]; - } - if(cfg.exists("num_buffers")) { - dev_data->bufcnt = (int)(cfg["num_buffers"]); - if(dev_data->bufcnt < 1) { - cerr<<"MiriSDR configuration error: num_buffers must be greater than 0\n"; - error(); - } - } - return 0; +int mirisdr_parse_config(input_t* const input, libconfig::Setting& cfg) { + mirisdr_dev_data_t* dev_data = (mirisdr_dev_data_t*)input->dev_data; + if (cfg.exists("serial")) { + dev_data->serial = strdup(cfg["serial"]); + } else if (cfg.exists("index")) { + dev_data->index = (int)cfg["index"]; + } else { + cerr << "MiriSDR configuration error: no index and no serial number given\n"; + error(); + } + if (cfg.exists("gain")) { + dev_data->gain = (int)cfg["gain"]; + } else { + cerr << "MiriSDR configuration error: gain is not configured\n"; + error(); + } + if (cfg.exists("correction")) { + dev_data->correction = (int)cfg["correction"]; + } + if (cfg.exists("num_buffers")) { + dev_data->bufcnt = (int)(cfg["num_buffers"]); + if (dev_data->bufcnt < 1) { + cerr << "MiriSDR configuration error: num_buffers must be greater than 0\n"; + error(); + } + } + return 0; } -MODULE_EXPORT input_t *mirisdr_input_new() { - mirisdr_dev_data_t *dev_data = (mirisdr_dev_data_t *)XCALLOC(1, sizeof(mirisdr_dev_data_t)); - dev_data->index = -1; // invalid default receiver index - dev_data->gain = -1; // invalid default gain value - dev_data->bufcnt = MIRISDR_DEFAULT_LIBUSB_BUFFER_COUNT; -/* return &( input_t ){ - .dev_data = dev_data, - .state = INPUT_UNKNOWN, - .sfmt = SFMT_U8, - .sample_rate = MIRISDR_DEFAULT_SAMPLE_RATE, - .parse_config = &mirisdr_parse_config, - .init = &mirisdr_init, - .run_rx_thread = &mirisdr_rx_thread, - .set_centerfreq = &mirisdr_set_centerfreq, - .stop = &mirisdr_stop - }; */ - input_t *input = (input_t *)XCALLOC(1, sizeof(input_t)); - input->dev_data = dev_data; - input->state = INPUT_UNKNOWN; - input->sfmt = SFMT_S8; - input->fullscale = (float)SCHAR_MAX - 0.5f; - input->bytes_per_sample = sizeof(char); - input->sample_rate = MIRISDR_DEFAULT_SAMPLE_RATE; - input->parse_config = &mirisdr_parse_config; - input->init = &mirisdr_init; - input->run_rx_thread = &mirisdr_rx_thread; - input->set_centerfreq = &mirisdr_set_centerfreq; - input->stop = &mirisdr_stop; - return input; +MODULE_EXPORT input_t* mirisdr_input_new() { + mirisdr_dev_data_t* dev_data = (mirisdr_dev_data_t*)XCALLOC(1, sizeof(mirisdr_dev_data_t)); + dev_data->index = -1; // invalid default receiver index + dev_data->gain = -1; // invalid default gain value + dev_data->bufcnt = MIRISDR_DEFAULT_LIBUSB_BUFFER_COUNT; + /* return &( input_t ){ + .dev_data = dev_data, + .state = INPUT_UNKNOWN, + .sfmt = SFMT_U8, + .sample_rate = MIRISDR_DEFAULT_SAMPLE_RATE, + .parse_config = &mirisdr_parse_config, + .init = &mirisdr_init, + .run_rx_thread = &mirisdr_rx_thread, + .set_centerfreq = &mirisdr_set_centerfreq, + .stop = &mirisdr_stop + }; */ + input_t* input = (input_t*)XCALLOC(1, sizeof(input_t)); + input->dev_data = dev_data; + input->state = INPUT_UNKNOWN; + input->sfmt = SFMT_S8; + input->fullscale = (float)SCHAR_MAX - 0.5f; + input->bytes_per_sample = sizeof(char); + input->sample_rate = MIRISDR_DEFAULT_SAMPLE_RATE; + input->parse_config = &mirisdr_parse_config; + input->init = &mirisdr_init; + input->run_rx_thread = &mirisdr_rx_thread; + input->set_centerfreq = &mirisdr_set_centerfreq; + input->stop = &mirisdr_stop; + return input; } diff --git a/src/input-mirisdr.h b/src/input-mirisdr.h index b12feed..f6df057 100644 --- a/src/input-mirisdr.h +++ b/src/input-mirisdr.h @@ -17,16 +17,16 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#include // mirisdr_dev_t +#include // mirisdr_dev_t #define MIRISDR_BUFSIZE 320000 #define MIRISDR_DEFAULT_LIBUSB_BUFFER_COUNT 10 #define MIRISDR_DEFAULT_SAMPLE_RATE 2560000 typedef struct { - mirisdr_dev_t *dev; // pointer to libmirisdr device struct - char *serial; // dongle serial number - int index; // dongle index - int correction; // correction in Hertz (PPM correction is not supported by libmirisdr) - int gain; // gain in dB - int bufcnt; // libusb buffer count + mirisdr_dev_t* dev; // pointer to libmirisdr device struct + char* serial; // dongle serial number + int index; // dongle index + int correction; // correction in Hertz (PPM correction is not supported by libmirisdr) + int gain; // gain in dB + int bufcnt; // libusb buffer count } mirisdr_dev_data_t; diff --git a/src/input-rtlsdr.cpp b/src/input-rtlsdr.cpp index 7a926b2..1752982 100644 --- a/src/input-rtlsdr.cpp +++ b/src/input-rtlsdr.cpp @@ -18,240 +18,237 @@ * along with this program. If not, see . */ +#include "input-rtlsdr.h" // rtlsdr_dev_data_t #include -#include -#include // SCHAR_MAX +#include // SCHAR_MAX +#include +#include // uint32_t #include -#include // uint32_t #include #include -#include // FIXME: get rid of this -#include // Setting -#include -#include "input-common.h" // input_t, sample_format_t, input_state_t, MODULE_EXPORT -#include "input-helpers.h" // circbuffer_append -#include "input-rtlsdr.h" // rtlsdr_dev_data_t -#include "rtl_airband.h" // do_exit, fft_size, debug_print, XCALLOC, error() +#include // FIXME: get rid of this +#include +#include // Setting +#include "input-common.h" // input_t, sample_format_t, input_state_t, MODULE_EXPORT +#include "input-helpers.h" // circbuffer_append +#include "rtl_airband.h" // do_exit, fft_size, debug_print, XCALLOC, error() using namespace std; -static void rtlsdr_callback(unsigned char *buf, uint32_t len, void *ctx) { - if(do_exit) return; - input_t *input = (input_t *)ctx; - circbuffer_append(input, buf, (size_t)len); +static void rtlsdr_callback(unsigned char* buf, uint32_t len, void* ctx) { + if (do_exit) + return; + input_t* input = (input_t*)ctx; + circbuffer_append(input, buf, (size_t)len); } /* based on librtlsdr-keenerd, (c) Kyle Keen */ -static bool rtlsdr_nearest_gain(rtlsdr_dev_t *dev, int target_gain, int *nearest) { - assert(nearest != NULL); - int i, r, err1, err2, count; - int *gains; - r = rtlsdr_set_tuner_gain_mode(dev, 1); - if (r < 0) { - return false; - } - count = rtlsdr_get_tuner_gains(dev, NULL); - if (count <= 0) { - return false; - } - gains = (int *)XCALLOC(count, sizeof(int)); - count = rtlsdr_get_tuner_gains(dev, gains); - *nearest = gains[0]; - for (i = 0; i < count; i++) { - err1 = abs(target_gain - *nearest); - err2 = abs(target_gain - gains[i]); - if (err2 < err1) { - *nearest = gains[i]; - } - } - free(gains); - return true; +static bool rtlsdr_nearest_gain(rtlsdr_dev_t* dev, int target_gain, int* nearest) { + assert(nearest != NULL); + int i, r, err1, err2, count; + int* gains; + r = rtlsdr_set_tuner_gain_mode(dev, 1); + if (r < 0) { + return false; + } + count = rtlsdr_get_tuner_gains(dev, NULL); + if (count <= 0) { + return false; + } + gains = (int*)XCALLOC(count, sizeof(int)); + count = rtlsdr_get_tuner_gains(dev, gains); + *nearest = gains[0]; + for (i = 0; i < count; i++) { + err1 = abs(target_gain - *nearest); + err2 = abs(target_gain - gains[i]); + if (err2 < err1) { + *nearest = gains[i]; + } + } + free(gains); + return true; } -static int rtlsdr_find_device_by_serial(char const * const s) { - char vendor[256] = {0}, product[256] = {0}, serial[256] = {0}; - int count = rtlsdr_get_device_count(); - if(count < 1) { - return -1; - } - for(int i = 0; i < count; i++) { - rtlsdr_get_device_usb_strings(i, vendor, product, serial); - if (strcmp(s, serial) != 0) { - continue; - } - return i; - } - return -1; +static int rtlsdr_find_device_by_serial(char const* const s) { + char vendor[256] = {0}, product[256] = {0}, serial[256] = {0}; + int count = rtlsdr_get_device_count(); + if (count < 1) { + return -1; + } + for (int i = 0; i < count; i++) { + rtlsdr_get_device_usb_strings(i, vendor, product, serial); + if (strcmp(s, serial) != 0) { + continue; + } + return i; + } + return -1; } -int rtlsdr_init(input_t * const input) { - rtlsdr_dev_data_t *dev_data = (rtlsdr_dev_data_t *)input->dev_data; - if(dev_data->serial != NULL) { - dev_data->index = rtlsdr_find_device_by_serial(dev_data->serial); - if(dev_data->index < 0) { - cerr<<"RTLSDR device with serial number "<serial<<" not found\n"; - error(); - } - } - - dev_data->dev = NULL; - rtlsdr_open(&dev_data->dev, dev_data->index); - if(NULL == dev_data->dev) { - log(LOG_ERR, "Failed to open rtlsdr device #%d.\n", dev_data->index); - error(); - } - - rtlsdr_dev_t *rtl = dev_data->dev; - int r = rtlsdr_set_sample_rate(rtl, input->sample_rate); - if (r < 0) { - log(LOG_ERR, "Failed to set sample rate for device #%d. Error %d.\n", dev_data->index, r); - } - - r = rtlsdr_set_center_freq(rtl, input->centerfreq); - if(r < 0) { - log(LOG_ERR, "Failed to set center freq for device #%d. Error %d.\n", dev_data->index, r); - } - - r = rtlsdr_set_freq_correction(rtl, dev_data->correction); - if(r < 0 && r != -2 ) { - log(LOG_ERR, "Failed to set freq correction for device #%d. Error %d.\n", dev_data->index, r); - } - - // Fitipower FC0012 gain needs to be initialized to its lowest value before setting it to the desired value - if (rtlsdr_get_tuner_type(rtl) == RTLSDR_TUNER_FC0012) { - int initialGain = 0; - if(rtlsdr_nearest_gain(rtl, -99, &initialGain) != true) { - log(LOG_ERR, "Failed to read supported gain list for device #%d\n", dev_data->index); - error(); - } - - r |= rtlsdr_set_tuner_gain(rtl, initialGain); - if (r < 0) { - log(LOG_ERR, "Failed to initialize gain for device #%d: error %d\n", - (float)initialGain / 10.f, dev_data->index, r); - } - } - - int ngain = 0; - if(rtlsdr_nearest_gain(rtl, dev_data->gain, &ngain) != true) { - log(LOG_ERR, "Failed to read supported gain list for device #%d\n", dev_data->index); - error(); - } - r = rtlsdr_set_tuner_gain_mode(rtl, 1); - r |= rtlsdr_set_tuner_gain(rtl, ngain); - if (r < 0) { - log(LOG_ERR, "Failed to set gain to %0.2f for device #%d: error %d\n", - (float)ngain / 10.f, dev_data->index, r); - } else { - log(LOG_INFO, "Device #%d: gain set to %0.2f dB\n", dev_data->index, - (float)rtlsdr_get_tuner_gain(rtl) / 10.f); - } - - r = rtlsdr_set_agc_mode(rtl, 0); - if (r < 0) { - log(LOG_ERR, "Failed to disable AGC for device #%d. Error %d.\n", dev_data->index, r); - } - rtlsdr_reset_buffer(rtl); - log(LOG_INFO, "RTLSDR device %d initialized\n", dev_data->index); - return 0; +int rtlsdr_init(input_t* const input) { + rtlsdr_dev_data_t* dev_data = (rtlsdr_dev_data_t*)input->dev_data; + if (dev_data->serial != NULL) { + dev_data->index = rtlsdr_find_device_by_serial(dev_data->serial); + if (dev_data->index < 0) { + cerr << "RTLSDR device with serial number " << dev_data->serial << " not found\n"; + error(); + } + } + + dev_data->dev = NULL; + rtlsdr_open(&dev_data->dev, dev_data->index); + if (NULL == dev_data->dev) { + log(LOG_ERR, "Failed to open rtlsdr device #%d.\n", dev_data->index); + error(); + } + + rtlsdr_dev_t* rtl = dev_data->dev; + int r = rtlsdr_set_sample_rate(rtl, input->sample_rate); + if (r < 0) { + log(LOG_ERR, "Failed to set sample rate for device #%d. Error %d.\n", dev_data->index, r); + } + + r = rtlsdr_set_center_freq(rtl, input->centerfreq); + if (r < 0) { + log(LOG_ERR, "Failed to set center freq for device #%d. Error %d.\n", dev_data->index, r); + } + + r = rtlsdr_set_freq_correction(rtl, dev_data->correction); + if (r < 0 && r != -2) { + log(LOG_ERR, "Failed to set freq correction for device #%d. Error %d.\n", dev_data->index, r); + } + + // Fitipower FC0012 gain needs to be initialized to its lowest value before setting it to the desired value + if (rtlsdr_get_tuner_type(rtl) == RTLSDR_TUNER_FC0012) { + int initialGain = 0; + if (rtlsdr_nearest_gain(rtl, -99, &initialGain) != true) { + log(LOG_ERR, "Failed to read supported gain list for device #%d\n", dev_data->index); + error(); + } + + r |= rtlsdr_set_tuner_gain(rtl, initialGain); + if (r < 0) { + log(LOG_ERR, "Failed to initialize gain for device #%d: error %d\n", (float)initialGain / 10.f, dev_data->index, r); + } + } + + int ngain = 0; + if (rtlsdr_nearest_gain(rtl, dev_data->gain, &ngain) != true) { + log(LOG_ERR, "Failed to read supported gain list for device #%d\n", dev_data->index); + error(); + } + r = rtlsdr_set_tuner_gain_mode(rtl, 1); + r |= rtlsdr_set_tuner_gain(rtl, ngain); + if (r < 0) { + log(LOG_ERR, "Failed to set gain to %0.2f for device #%d: error %d\n", (float)ngain / 10.f, dev_data->index, r); + } else { + log(LOG_INFO, "Device #%d: gain set to %0.2f dB\n", dev_data->index, (float)rtlsdr_get_tuner_gain(rtl) / 10.f); + } + + r = rtlsdr_set_agc_mode(rtl, 0); + if (r < 0) { + log(LOG_ERR, "Failed to disable AGC for device #%d. Error %d.\n", dev_data->index, r); + } + rtlsdr_reset_buffer(rtl); + log(LOG_INFO, "RTLSDR device %d initialized\n", dev_data->index); + return 0; } -void *rtlsdr_rx_thread(void *ctx) { - input_t *input = (input_t *)ctx; - rtlsdr_dev_data_t *dev_data = (rtlsdr_dev_data_t *)input->dev_data; - assert(dev_data->dev != NULL); - - input->state = INPUT_RUNNING; - if(rtlsdr_read_async(dev_data->dev, rtlsdr_callback, ctx, dev_data->bufcnt, RTLSDR_BUFSIZE) < 0) { - log(LOG_ERR, "RTLSDR device #%d: async read failed, disabling\n", dev_data->index); - input->state = INPUT_FAILED; - } - return 0; +void* rtlsdr_rx_thread(void* ctx) { + input_t* input = (input_t*)ctx; + rtlsdr_dev_data_t* dev_data = (rtlsdr_dev_data_t*)input->dev_data; + assert(dev_data->dev != NULL); + + input->state = INPUT_RUNNING; + if (rtlsdr_read_async(dev_data->dev, rtlsdr_callback, ctx, dev_data->bufcnt, RTLSDR_BUFSIZE) < 0) { + log(LOG_ERR, "RTLSDR device #%d: async read failed, disabling\n", dev_data->index); + input->state = INPUT_FAILED; + } + return 0; } -int rtlsdr_stop(input_t * const input) { - rtlsdr_dev_data_t *dev_data = (rtlsdr_dev_data_t *)input->dev_data; - assert(dev_data->dev != NULL); +int rtlsdr_stop(input_t* const input) { + rtlsdr_dev_data_t* dev_data = (rtlsdr_dev_data_t*)input->dev_data; + assert(dev_data->dev != NULL); - if(rtlsdr_cancel_async(dev_data->dev) < 0) { - return -1; - } - return rtlsdr_close(dev_data->dev); + if (rtlsdr_cancel_async(dev_data->dev) < 0) { + return -1; + } + return rtlsdr_close(dev_data->dev); } -int rtlsdr_set_centerfreq(input_t * const input, int const centerfreq) { - rtlsdr_dev_data_t *dev_data = (rtlsdr_dev_data_t *)input->dev_data; - assert(dev_data->dev != NULL); +int rtlsdr_set_centerfreq(input_t* const input, int const centerfreq) { + rtlsdr_dev_data_t* dev_data = (rtlsdr_dev_data_t*)input->dev_data; + assert(dev_data->dev != NULL); - int r = rtlsdr_set_center_freq(dev_data->dev, centerfreq); - if(r < 0) { - log(LOG_ERR, "Failed to set centerfreq for RTLSDR device #%d: error %d\n", - dev_data->index, r); - return -1; - } - return 0; + int r = rtlsdr_set_center_freq(dev_data->dev, centerfreq); + if (r < 0) { + log(LOG_ERR, "Failed to set centerfreq for RTLSDR device #%d: error %d\n", dev_data->index, r); + return -1; + } + return 0; } -int rtlsdr_parse_config(input_t * const input, libconfig::Setting &cfg) { - rtlsdr_dev_data_t *dev_data = (rtlsdr_dev_data_t *)input->dev_data; - if(cfg.exists("serial")) { - dev_data->serial = strdup(cfg["serial"]); - } else if(cfg.exists("index")) { - dev_data->index = (int)cfg["index"]; - } else { - cerr<<"RTLSDR configuration error: no index and no serial number given\n"; - error(); - } - if(cfg.exists("gain")) { - if(cfg["gain"].getType() == libconfig::Setting::TypeInt) { // backward compatibility - dev_data->gain = (int)cfg["gain"] * 10; - } else if(cfg["gain"].getType() == libconfig::Setting::TypeFloat) { - dev_data->gain = (int)((float)cfg["gain"] * 10.0f); - } - } else { - cerr<<"RTLSDR configuration error: gain is not configured\n"; - error(); - } - if(cfg.exists("correction")) { - dev_data->correction = (int)cfg["correction"]; - } - if(cfg.exists("buffers")) { - dev_data->bufcnt = (int)(cfg["buffers"]); - if(dev_data->bufcnt < 1) { - cerr<<"RTLSDR configuration error: buffers must be greater than 0\n"; - error(); - } - } - return 0; +int rtlsdr_parse_config(input_t* const input, libconfig::Setting& cfg) { + rtlsdr_dev_data_t* dev_data = (rtlsdr_dev_data_t*)input->dev_data; + if (cfg.exists("serial")) { + dev_data->serial = strdup(cfg["serial"]); + } else if (cfg.exists("index")) { + dev_data->index = (int)cfg["index"]; + } else { + cerr << "RTLSDR configuration error: no index and no serial number given\n"; + error(); + } + if (cfg.exists("gain")) { + if (cfg["gain"].getType() == libconfig::Setting::TypeInt) { // backward compatibility + dev_data->gain = (int)cfg["gain"] * 10; + } else if (cfg["gain"].getType() == libconfig::Setting::TypeFloat) { + dev_data->gain = (int)((float)cfg["gain"] * 10.0f); + } + } else { + cerr << "RTLSDR configuration error: gain is not configured\n"; + error(); + } + if (cfg.exists("correction")) { + dev_data->correction = (int)cfg["correction"]; + } + if (cfg.exists("buffers")) { + dev_data->bufcnt = (int)(cfg["buffers"]); + if (dev_data->bufcnt < 1) { + cerr << "RTLSDR configuration error: buffers must be greater than 0\n"; + error(); + } + } + return 0; } -MODULE_EXPORT input_t *rtlsdr_input_new() { - rtlsdr_dev_data_t *dev_data = (rtlsdr_dev_data_t *)XCALLOC(1, sizeof(rtlsdr_dev_data_t)); - dev_data->index = -1; // invalid default receiver index - dev_data->gain = -1; // invalid default gain value - dev_data->bufcnt = RTLSDR_DEFAULT_LIBUSB_BUFFER_COUNT; -/* return &( input_t ){ - .dev_data = dev_data, - .state = INPUT_UNKNOWN, - .sfmt = SFMT_U8, - .sample_rate = RTLSDR_DEFAULT_SAMPLE_RATE, - .parse_config = &rtlsdr_parse_config, - .init = &rtlsdr_init, - .run_rx_thread = &rtlsdr_rx_thread, - .set_centerfreq = &rtlsdr_set_centerfreq, - .stop = &rtlsdr_stop - }; */ - input_t *input = (input_t *)XCALLOC(1, sizeof(input_t)); - input->dev_data = dev_data; - input->state = INPUT_UNKNOWN; - input->sfmt = SFMT_U8; - input->fullscale = (float)SCHAR_MAX - 0.5f; - input->bytes_per_sample = sizeof(unsigned char); - input->sample_rate = RTLSDR_DEFAULT_SAMPLE_RATE; - input->parse_config = &rtlsdr_parse_config; - input->init = &rtlsdr_init; - input->run_rx_thread = &rtlsdr_rx_thread; - input->set_centerfreq = &rtlsdr_set_centerfreq; - input->stop = &rtlsdr_stop; - return input; +MODULE_EXPORT input_t* rtlsdr_input_new() { + rtlsdr_dev_data_t* dev_data = (rtlsdr_dev_data_t*)XCALLOC(1, sizeof(rtlsdr_dev_data_t)); + dev_data->index = -1; // invalid default receiver index + dev_data->gain = -1; // invalid default gain value + dev_data->bufcnt = RTLSDR_DEFAULT_LIBUSB_BUFFER_COUNT; + /* return &( input_t ){ + .dev_data = dev_data, + .state = INPUT_UNKNOWN, + .sfmt = SFMT_U8, + .sample_rate = RTLSDR_DEFAULT_SAMPLE_RATE, + .parse_config = &rtlsdr_parse_config, + .init = &rtlsdr_init, + .run_rx_thread = &rtlsdr_rx_thread, + .set_centerfreq = &rtlsdr_set_centerfreq, + .stop = &rtlsdr_stop + }; */ + input_t* input = (input_t*)XCALLOC(1, sizeof(input_t)); + input->dev_data = dev_data; + input->state = INPUT_UNKNOWN; + input->sfmt = SFMT_U8; + input->fullscale = (float)SCHAR_MAX - 0.5f; + input->bytes_per_sample = sizeof(unsigned char); + input->sample_rate = RTLSDR_DEFAULT_SAMPLE_RATE; + input->parse_config = &rtlsdr_parse_config; + input->init = &rtlsdr_init; + input->run_rx_thread = &rtlsdr_rx_thread; + input->set_centerfreq = &rtlsdr_set_centerfreq; + input->stop = &rtlsdr_stop; + return input; } diff --git a/src/input-rtlsdr.h b/src/input-rtlsdr.h index 31dbdd6..b02087c 100644 --- a/src/input-rtlsdr.h +++ b/src/input-rtlsdr.h @@ -17,17 +17,16 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#include // rtlsdr_dev_t +#include // rtlsdr_dev_t #define RTLSDR_BUFSIZE 320000 #define RTLSDR_DEFAULT_LIBUSB_BUFFER_COUNT 10 #define RTLSDR_DEFAULT_SAMPLE_RATE 2560000 typedef struct { - rtlsdr_dev_t *dev; // pointer to librtlsdr device struct - char *serial; // dongle serial number - int index; // dongle index - int correction; // PPM correction - int gain; // gain in tenths of dB - int bufcnt; // libusb buffer count + rtlsdr_dev_t* dev; // pointer to librtlsdr device struct + char* serial; // dongle serial number + int index; // dongle index + int correction; // PPM correction + int gain; // gain in tenths of dB + int bufcnt; // libusb buffer count } rtlsdr_dev_data_t; - diff --git a/src/input-soapysdr.cpp b/src/input-soapysdr.cpp index 53c1731..c7f0878 100644 --- a/src/input-soapysdr.cpp +++ b/src/input-soapysdr.cpp @@ -18,21 +18,21 @@ * along with this program. If not, see . */ -#include +#include "input-soapysdr.h" // soapysdr_dev_data_t +#include // SoapySDRDevice, SoapySDRDevice_makeStrArgs +#include // SOAPY_SDR_CS constants +#include // SOAPY_SDR_API_VERSION #include -#include // SCHAR_MAX, SHRT_MAX -#include // round -#include // calloc -#include // memcpy, strcmp -#include // LOG_* macros -#include // Setting -#include // SOAPY_SDR_API_VERSION -#include // SoapySDRDevice, SoapySDRDevice_makeStrArgs -#include // SOAPY_SDR_CS constants -#include "input-common.h" // input_t, sample_format_t, input_state_t, MODULE_EXPORT -#include "input-helpers.h" // circbuffer_append -#include "input-soapysdr.h" // soapysdr_dev_data_t -#include "rtl_airband.h" // do_exit, fft_size, debug_print, XCALLOC, error() +#include // SCHAR_MAX, SHRT_MAX +#include // round +#include // calloc +#include // memcpy, strcmp +#include // LOG_* macros +#include +#include // Setting +#include "input-common.h" // input_t, sample_format_t, input_state_t, MODULE_EXPORT +#include "input-helpers.h" // circbuffer_append +#include "rtl_airband.h" // do_exit, fft_size, debug_print, XCALLOC, error() using namespace std; @@ -42,356 +42,325 @@ using namespace std; // If fullscale is > 0, it means it has been read by // SoapySDRDevice_getNativeStreamFormat, so we treat this value as valid. // Otherwise, guess a suitable default value. -static bool soapysdr_match_sfmt(input_t * const input, char const * const fmt, double const fullscale) { - if(strcmp(fmt, SOAPY_SDR_CU8) == 0) { - input->sfmt = SFMT_U8; - input->bytes_per_sample = sizeof(unsigned char); - input->fullscale = (fullscale > 0 ? fullscale : (float)SCHAR_MAX - 0.5f); - goto matched; - } else if(strcmp(fmt, SOAPY_SDR_CS8) == 0) { - input->sfmt = SFMT_S8; - input->bytes_per_sample = sizeof(char); - input->fullscale = (fullscale > 0 ? fullscale : (float)SCHAR_MAX - 0.5f); - goto matched; - } else if(strcmp(fmt, SOAPY_SDR_CS16) == 0) { - input->sfmt = SFMT_S16; - input->bytes_per_sample = sizeof(short); - input->fullscale = (fullscale > 0 ? fullscale : (float)SHRT_MAX - 0.5f); - goto matched; - } else if(strcmp(fmt, SOAPY_SDR_CF32) == 0) { - input->sfmt = SFMT_F32; - input->bytes_per_sample = sizeof(float); - input->fullscale = (fullscale > 0 ? fullscale : 1.0f); - goto matched; - } - return false; +static bool soapysdr_match_sfmt(input_t* const input, char const* const fmt, double const fullscale) { + if (strcmp(fmt, SOAPY_SDR_CU8) == 0) { + input->sfmt = SFMT_U8; + input->bytes_per_sample = sizeof(unsigned char); + input->fullscale = (fullscale > 0 ? fullscale : (float)SCHAR_MAX - 0.5f); + goto matched; + } else if (strcmp(fmt, SOAPY_SDR_CS8) == 0) { + input->sfmt = SFMT_S8; + input->bytes_per_sample = sizeof(char); + input->fullscale = (fullscale > 0 ? fullscale : (float)SCHAR_MAX - 0.5f); + goto matched; + } else if (strcmp(fmt, SOAPY_SDR_CS16) == 0) { + input->sfmt = SFMT_S16; + input->bytes_per_sample = sizeof(short); + input->fullscale = (fullscale > 0 ? fullscale : (float)SHRT_MAX - 0.5f); + goto matched; + } else if (strcmp(fmt, SOAPY_SDR_CF32) == 0) { + input->sfmt = SFMT_F32; + input->bytes_per_sample = sizeof(float); + input->fullscale = (fullscale > 0 ? fullscale : 1.0f); + goto matched; + } + return false; matched: - soapysdr_dev_data_t *dev_data = (soapysdr_dev_data_t *)input->dev_data; - dev_data->sample_format = strdup(fmt); - return true; + soapysdr_dev_data_t* dev_data = (soapysdr_dev_data_t*)input->dev_data; + dev_data->sample_format = strdup(fmt); + return true; } // Choose a suitable sample format. // Bail out if no supported sample format is found. -static bool soapysdr_choose_sample_format(SoapySDRDevice * const sdr, input_t * const input) { - bool ret = false; - size_t len = 0; - char **formats = NULL; - soapysdr_dev_data_t *dev_data = (soapysdr_dev_data_t *)input->dev_data; - input->sfmt = SFMT_UNDEF; -// First try device's native format to avoid extra conversion - double fullscale = 0.0; - char *fmt = SoapySDRDevice_getNativeStreamFormat(sdr, SOAPY_SDR_RX, dev_data->channel, &fullscale); +static bool soapysdr_choose_sample_format(SoapySDRDevice* const sdr, input_t* const input) { + bool ret = false; + size_t len = 0; + char** formats = NULL; + soapysdr_dev_data_t* dev_data = (soapysdr_dev_data_t*)input->dev_data; + input->sfmt = SFMT_UNDEF; + // First try device's native format to avoid extra conversion + double fullscale = 0.0; + char* fmt = SoapySDRDevice_getNativeStreamFormat(sdr, SOAPY_SDR_RX, dev_data->channel, &fullscale); - if(soapysdr_match_sfmt(input, fmt, fullscale) == true) { - log(LOG_NOTICE, "SoapySDR: device '%s': using native sample format '%s' (fullScale=%.1f)\n", - dev_data->device_string, fmt, input->fullscale); - ret = true; - goto end; - } -// Native format is not supported by rtl_airband; find out if there is anything else. - formats = SoapySDRDevice_getStreamFormats(sdr, SOAPY_SDR_RX, dev_data->channel, &len); - if(formats == NULL || len == 0) { - log(LOG_ERR, "SoapySDR: device '%s': failed to read supported sample formats\n", - dev_data->device_string); - ret = false; - goto end; - } - for(size_t i = 0; i < len; i++) { - if(soapysdr_match_sfmt(input, formats[i], -1.0) == true) { - log(LOG_NOTICE, "SoapySDR: device '%s': using non-native sample format '%s' (assuming fullScale=%.1f)\n", - dev_data->device_string, formats[i], input->fullscale); - ret = true; - goto end; - } - } -// Nothing found; we can't use this device. - log(LOG_ERR, "SoapySDR: device '%s': no supported sample format found\n", dev_data->device_string); + if (soapysdr_match_sfmt(input, fmt, fullscale) == true) { + log(LOG_NOTICE, "SoapySDR: device '%s': using native sample format '%s' (fullScale=%.1f)\n", dev_data->device_string, fmt, input->fullscale); + ret = true; + goto end; + } + // Native format is not supported by rtl_airband; find out if there is anything else. + formats = SoapySDRDevice_getStreamFormats(sdr, SOAPY_SDR_RX, dev_data->channel, &len); + if (formats == NULL || len == 0) { + log(LOG_ERR, "SoapySDR: device '%s': failed to read supported sample formats\n", dev_data->device_string); + ret = false; + goto end; + } + for (size_t i = 0; i < len; i++) { + if (soapysdr_match_sfmt(input, formats[i], -1.0) == true) { + log(LOG_NOTICE, "SoapySDR: device '%s': using non-native sample format '%s' (assuming fullScale=%.1f)\n", dev_data->device_string, formats[i], input->fullscale); + ret = true; + goto end; + } + } + // Nothing found; we can't use this device. + log(LOG_ERR, "SoapySDR: device '%s': no supported sample format found\n", dev_data->device_string); end: - return ret; + return ret; } -static int sdrplay_get_nearest_sample_rate(SoapySDRDevice *sdr, int channel, int sample_rate) { - size_t len = 0; - double sr = (double)sample_rate; - SoapySDRRange *range = SoapySDRDevice_getSampleRateRange(sdr, SOAPY_SDR_RX, channel, &len); - if(range == NULL) { - log(LOG_ERR, "SoapySDR: failed to read supported sampling rate ranges from the device\n"); - return -1; - } - debug_print("Got %zu ranges\n", len); - double nearest_rate = range[0].minimum; - double offset1, offset2; - for(size_t i = 0; i < len; i++) { - debug_print("sr=%.1f min=%.1f max=%.1f step=%.1f\n", sr, range[i].minimum, range[i].maximum, range[i].step); - if(sr >= range[i].minimum && sr <= range[i].maximum) { - debug_print("Found suitable range: min=%.0f max=%0.f step=%0.f\n", - range[i].minimum, range[i].maximum, range[i].step); - if(range[i].step == 0.0 || range[i].step >= (range[i].maximum - range[i].minimum)) { - return (int)(range[i].maximum - sr > sr - range[i].minimum ? - range[i].minimum : range[i].maximum); - } - sr = (int)(range[i].minimum + range[i].step * round((sr - range[i].minimum) / range[i].step)); - if(sr > range[i].maximum) { - sr = (int)range[i].maximum; - } - return (int)sr; - } else { - offset1 = abs(sr - nearest_rate); - offset2 = abs(sr - range[i].minimum); - if(offset2 < offset1) - nearest_rate = range[i].minimum; - offset1 = abs(sr - nearest_rate); - offset2 = abs(sr - range[i].maximum); - if(offset2 < offset1) - nearest_rate = range[i].maximum; - } - } - return (int)nearest_rate; +static int sdrplay_get_nearest_sample_rate(SoapySDRDevice* sdr, int channel, int sample_rate) { + size_t len = 0; + double sr = (double)sample_rate; + SoapySDRRange* range = SoapySDRDevice_getSampleRateRange(sdr, SOAPY_SDR_RX, channel, &len); + if (range == NULL) { + log(LOG_ERR, "SoapySDR: failed to read supported sampling rate ranges from the device\n"); + return -1; + } + debug_print("Got %zu ranges\n", len); + double nearest_rate = range[0].minimum; + double offset1, offset2; + for (size_t i = 0; i < len; i++) { + debug_print("sr=%.1f min=%.1f max=%.1f step=%.1f\n", sr, range[i].minimum, range[i].maximum, range[i].step); + if (sr >= range[i].minimum && sr <= range[i].maximum) { + debug_print("Found suitable range: min=%.0f max=%0.f step=%0.f\n", range[i].minimum, range[i].maximum, range[i].step); + if (range[i].step == 0.0 || range[i].step >= (range[i].maximum - range[i].minimum)) { + return (int)(range[i].maximum - sr > sr - range[i].minimum ? range[i].minimum : range[i].maximum); + } + sr = (int)(range[i].minimum + range[i].step * round((sr - range[i].minimum) / range[i].step)); + if (sr > range[i].maximum) { + sr = (int)range[i].maximum; + } + return (int)sr; + } else { + offset1 = abs(sr - nearest_rate); + offset2 = abs(sr - range[i].minimum); + if (offset2 < offset1) + nearest_rate = range[i].minimum; + offset1 = abs(sr - nearest_rate); + offset2 = abs(sr - range[i].maximum); + if (offset2 < offset1) + nearest_rate = range[i].maximum; + } + } + return (int)nearest_rate; } -int soapysdr_parse_config(input_t * const input, libconfig::Setting &cfg) { - soapysdr_dev_data_t *dev_data = (soapysdr_dev_data_t *)input->dev_data; +int soapysdr_parse_config(input_t* const input, libconfig::Setting& cfg) { + soapysdr_dev_data_t* dev_data = (soapysdr_dev_data_t*)input->dev_data; - if(cfg.exists("device_string")) { - dev_data->device_string = strdup(cfg["device_string"]); - } else { - cerr<<"SoapySDR configuration error: mandatory parameter missing: device_string\n"; - error(); - } - if(cfg.exists("gain")) { - if(cfg["gain"].getType() == libconfig::Setting::TypeInt) { - dev_data->gain = (double)((int)cfg["gain"]); - } else if(cfg["gain"].getType() == libconfig::Setting::TypeFloat) { - dev_data->gain = (double)cfg["gain"]; - } else { -// Either it's a string or an unsupported type which will cause an exception - this is fine - dev_data->gains = SoapySDRKwargs_fromString((const char *)cfg["gain"]); - if(dev_data->gains.size < 1) { - cerr<<"SoapySDR configuration error: device '"<device_string<< - "': gain: syntax error (must be a sequence of 'name1=value1,name2=value2,...')\n"; - error(); - } - } - dev_data->agc = false; - } else { - dev_data->agc = true; - } - if(cfg.exists("correction")) { - if(cfg["correction"].getType() == libconfig::Setting::TypeInt) { - dev_data->correction = (double)((int)cfg["correction"]); - } else if(cfg["correction"].getType() == libconfig::Setting::TypeFloat) { - dev_data->correction = (float)cfg["correction"]; - } else { - cerr<<"SoapySDR configuration error: device '"<device_string<< - "': correction value must be numeric\n"; - error(); - } - } - if(cfg.exists("channel")) { - dev_data->channel = (size_t)(int)cfg["channel"]; - } - if(cfg.exists("antenna")) { - dev_data->antenna = strdup(cfg["antenna"]); - } -// Find a suitable sample format and sample rate (unless set in the config) -// based on device capabilities. -// We have to do this here and not in soapysdr_init, because parse_devices() -// requires sample_rate and bytes_per_sample to be set correctly in order to -// calculate the size of the sample buffer, which has to be done before -// soapysdr_init() is run. - SoapySDRDevice *sdr = SoapySDRDevice_makeStrArgs(dev_data->device_string); - if (sdr == NULL) { - log(LOG_ERR, "Failed to open SoapySDR device '%s': %s\n", dev_data->device_string, - SoapySDRDevice_lastError()); - error(); - } - if(soapysdr_choose_sample_format(sdr, input) == false) { - cerr<<"SoapySDR configuration error: device '"<device_string<< - "': no suitable sample format found\n"; - error(); - } - if(input->sample_rate < 0) { - input->sample_rate = sdrplay_get_nearest_sample_rate(sdr, dev_data->channel, SOAPYSDR_DEFAULT_SAMPLE_RATE); - if(input->sample_rate < 0) { - log(LOG_ERR, "Failed to find a suitable sample rate for SoapySDR device '%s'\n", - dev_data->device_string); - log(LOG_ERR, "Specify a supported value using \"sample_rate\" option in the device configuration\n"); - error(); - } - } - SoapySDRDevice_unmake(sdr); - return 0; + if (cfg.exists("device_string")) { + dev_data->device_string = strdup(cfg["device_string"]); + } else { + cerr << "SoapySDR configuration error: mandatory parameter missing: device_string\n"; + error(); + } + if (cfg.exists("gain")) { + if (cfg["gain"].getType() == libconfig::Setting::TypeInt) { + dev_data->gain = (double)((int)cfg["gain"]); + } else if (cfg["gain"].getType() == libconfig::Setting::TypeFloat) { + dev_data->gain = (double)cfg["gain"]; + } else { + // Either it's a string or an unsupported type which will cause an exception - this is fine + dev_data->gains = SoapySDRKwargs_fromString((const char*)cfg["gain"]); + if (dev_data->gains.size < 1) { + cerr << "SoapySDR configuration error: device '" << dev_data->device_string << "': gain: syntax error (must be a sequence of 'name1=value1,name2=value2,...')\n"; + error(); + } + } + dev_data->agc = false; + } else { + dev_data->agc = true; + } + if (cfg.exists("correction")) { + if (cfg["correction"].getType() == libconfig::Setting::TypeInt) { + dev_data->correction = (double)((int)cfg["correction"]); + } else if (cfg["correction"].getType() == libconfig::Setting::TypeFloat) { + dev_data->correction = (float)cfg["correction"]; + } else { + cerr << "SoapySDR configuration error: device '" << dev_data->device_string << "': correction value must be numeric\n"; + error(); + } + } + if (cfg.exists("channel")) { + dev_data->channel = (size_t)(int)cfg["channel"]; + } + if (cfg.exists("antenna")) { + dev_data->antenna = strdup(cfg["antenna"]); + } + // Find a suitable sample format and sample rate (unless set in the config) + // based on device capabilities. + // We have to do this here and not in soapysdr_init, because parse_devices() + // requires sample_rate and bytes_per_sample to be set correctly in order to + // calculate the size of the sample buffer, which has to be done before + // soapysdr_init() is run. + SoapySDRDevice* sdr = SoapySDRDevice_makeStrArgs(dev_data->device_string); + if (sdr == NULL) { + log(LOG_ERR, "Failed to open SoapySDR device '%s': %s\n", dev_data->device_string, SoapySDRDevice_lastError()); + error(); + } + if (soapysdr_choose_sample_format(sdr, input) == false) { + cerr << "SoapySDR configuration error: device '" << dev_data->device_string << "': no suitable sample format found\n"; + error(); + } + if (input->sample_rate < 0) { + input->sample_rate = sdrplay_get_nearest_sample_rate(sdr, dev_data->channel, SOAPYSDR_DEFAULT_SAMPLE_RATE); + if (input->sample_rate < 0) { + log(LOG_ERR, "Failed to find a suitable sample rate for SoapySDR device '%s'\n", dev_data->device_string); + log(LOG_ERR, "Specify a supported value using \"sample_rate\" option in the device configuration\n"); + error(); + } + } + SoapySDRDevice_unmake(sdr); + return 0; } -int soapysdr_init(input_t * const input) { - soapysdr_dev_data_t *dev_data = (soapysdr_dev_data_t *)input->dev_data; +int soapysdr_init(input_t* const input) { + soapysdr_dev_data_t* dev_data = (soapysdr_dev_data_t*)input->dev_data; - dev_data->dev = SoapySDRDevice_makeStrArgs(dev_data->device_string); - if (dev_data->dev == NULL) { - log(LOG_ERR, "Failed to open SoapySDR device '%s': %s\n", dev_data->device_string, - SoapySDRDevice_lastError()); - error(); - } - SoapySDRDevice *sdr = dev_data->dev; + dev_data->dev = SoapySDRDevice_makeStrArgs(dev_data->device_string); + if (dev_data->dev == NULL) { + log(LOG_ERR, "Failed to open SoapySDR device '%s': %s\n", dev_data->device_string, SoapySDRDevice_lastError()); + error(); + } + SoapySDRDevice* sdr = dev_data->dev; - if(SoapySDRDevice_setSampleRate(sdr, SOAPY_SDR_RX, dev_data->channel, input->sample_rate) != 0) { - log(LOG_ERR, "Failed to set sample rate for SoapySDR device '%s': %s\n", - dev_data->device_string, SoapySDRDevice_lastError()); - error(); - } - log(LOG_INFO, "SoapySDR: device '%s': sample rate set to %.0f sps\n", - dev_data->device_string, SoapySDRDevice_getSampleRate(sdr, SOAPY_SDR_RX, dev_data->channel)); - if(SoapySDRDevice_setFrequency(sdr, SOAPY_SDR_RX, dev_data->channel, input->centerfreq, NULL) != 0) { - log(LOG_ERR, "Failed to set frequency for SoapySDR device '%s': %s\n", - dev_data->device_string, SoapySDRDevice_lastError()); - error(); - } - if(SoapySDRDevice_setFrequencyCorrection(sdr, SOAPY_SDR_RX, dev_data->channel, dev_data->correction) != 0) { - log(LOG_ERR, "Failed to set frequency correction for SoapySDR device '%s': %s\n", - dev_data->device_string, SoapySDRDevice_lastError()); - error(); - } - if(dev_data->antenna != NULL) { - if(SoapySDRDevice_setAntenna(sdr, SOAPY_SDR_RX, dev_data->channel, dev_data->antenna) != 0) { - log(LOG_ERR, "Failed to set antenna to '%s' for SoapySDR device '%s': %s\n", - dev_data->device_string, dev_data->antenna, SoapySDRDevice_lastError()); - error(); - } - log(LOG_INFO, "SoapySDR: device '%s': antenna set to '%s'\n", - dev_data->device_string, SoapySDRDevice_getAntenna(sdr, SOAPY_SDR_RX, dev_data->channel)); - } - if(SoapySDRDevice_setGainMode(sdr, SOAPY_SDR_RX, dev_data->channel, dev_data->agc) != 0) { - log(LOG_ERR, "Failed to %s AGC for SoapySDR device '%s': %s\n", - dev_data->agc ? "enable" : "disable", dev_data->device_string, SoapySDRDevice_lastError()); - error(); - } - log(LOG_INFO, "SoapySDR: device '%s': AGC %s (requested: %s)\n", dev_data->device_string, - SoapySDRDevice_getGainMode(sdr, SOAPY_SDR_RX, dev_data->channel) ? "on" : "off", - dev_data->agc ? "on" : "off"); - if(!dev_data->agc) { - if(dev_data->gains.size > 0) { - for(size_t i = 0; i < dev_data->gains.size; i++) { - char * const key = dev_data->gains.keys[i]; - double val = atof(dev_data->gains.vals[i]); - if(SoapySDRDevice_setGainElement(sdr, SOAPY_SDR_RX, dev_data->channel, key, val) != 0) { - log(LOG_ERR, "Failed to set gain element '%s' for SoapySDR device '%s': %s\n", - key, dev_data->device_string, SoapySDRDevice_lastError()); - error(); - } - log(LOG_INFO, "SoapySDR: device '%s': gain '%s' set to %.1f dB\n", - dev_data->device_string, key, - SoapySDRDevice_getGainElement(sdr, SOAPY_SDR_RX, dev_data->channel, key)); - } - } else { - if(SoapySDRDevice_setGain(sdr, SOAPY_SDR_RX, dev_data->channel, dev_data->gain) != 0) { - log(LOG_ERR, "Failed to set gain for SoapySDR device '%s': %s\n", - dev_data->device_string, SoapySDRDevice_lastError()); - error(); - } - log(LOG_INFO, "SoapySDR: device '%s': gain set to %.1f dB\n", - dev_data->device_string, SoapySDRDevice_getGain(sdr, SOAPY_SDR_RX, dev_data->channel)); - } - } - log(LOG_INFO, "SoapySDR: device '%s' initialized\n", dev_data->device_string); - return 0; + if (SoapySDRDevice_setSampleRate(sdr, SOAPY_SDR_RX, dev_data->channel, input->sample_rate) != 0) { + log(LOG_ERR, "Failed to set sample rate for SoapySDR device '%s': %s\n", dev_data->device_string, SoapySDRDevice_lastError()); + error(); + } + log(LOG_INFO, "SoapySDR: device '%s': sample rate set to %.0f sps\n", dev_data->device_string, SoapySDRDevice_getSampleRate(sdr, SOAPY_SDR_RX, dev_data->channel)); + if (SoapySDRDevice_setFrequency(sdr, SOAPY_SDR_RX, dev_data->channel, input->centerfreq, NULL) != 0) { + log(LOG_ERR, "Failed to set frequency for SoapySDR device '%s': %s\n", dev_data->device_string, SoapySDRDevice_lastError()); + error(); + } + if (SoapySDRDevice_setFrequencyCorrection(sdr, SOAPY_SDR_RX, dev_data->channel, dev_data->correction) != 0) { + log(LOG_ERR, "Failed to set frequency correction for SoapySDR device '%s': %s\n", dev_data->device_string, SoapySDRDevice_lastError()); + error(); + } + if (dev_data->antenna != NULL) { + if (SoapySDRDevice_setAntenna(sdr, SOAPY_SDR_RX, dev_data->channel, dev_data->antenna) != 0) { + log(LOG_ERR, "Failed to set antenna to '%s' for SoapySDR device '%s': %s\n", dev_data->device_string, dev_data->antenna, SoapySDRDevice_lastError()); + error(); + } + log(LOG_INFO, "SoapySDR: device '%s': antenna set to '%s'\n", dev_data->device_string, SoapySDRDevice_getAntenna(sdr, SOAPY_SDR_RX, dev_data->channel)); + } + if (SoapySDRDevice_setGainMode(sdr, SOAPY_SDR_RX, dev_data->channel, dev_data->agc) != 0) { + log(LOG_ERR, "Failed to %s AGC for SoapySDR device '%s': %s\n", dev_data->agc ? "enable" : "disable", dev_data->device_string, SoapySDRDevice_lastError()); + error(); + } + log(LOG_INFO, "SoapySDR: device '%s': AGC %s (requested: %s)\n", dev_data->device_string, SoapySDRDevice_getGainMode(sdr, SOAPY_SDR_RX, dev_data->channel) ? "on" : "off", + dev_data->agc ? "on" : "off"); + if (!dev_data->agc) { + if (dev_data->gains.size > 0) { + for (size_t i = 0; i < dev_data->gains.size; i++) { + char* const key = dev_data->gains.keys[i]; + double val = atof(dev_data->gains.vals[i]); + if (SoapySDRDevice_setGainElement(sdr, SOAPY_SDR_RX, dev_data->channel, key, val) != 0) { + log(LOG_ERR, "Failed to set gain element '%s' for SoapySDR device '%s': %s\n", key, dev_data->device_string, SoapySDRDevice_lastError()); + error(); + } + log(LOG_INFO, "SoapySDR: device '%s': gain '%s' set to %.1f dB\n", dev_data->device_string, key, SoapySDRDevice_getGainElement(sdr, SOAPY_SDR_RX, dev_data->channel, key)); + } + } else { + if (SoapySDRDevice_setGain(sdr, SOAPY_SDR_RX, dev_data->channel, dev_data->gain) != 0) { + log(LOG_ERR, "Failed to set gain for SoapySDR device '%s': %s\n", dev_data->device_string, SoapySDRDevice_lastError()); + error(); + } + log(LOG_INFO, "SoapySDR: device '%s': gain set to %.1f dB\n", dev_data->device_string, SoapySDRDevice_getGain(sdr, SOAPY_SDR_RX, dev_data->channel)); + } + } + log(LOG_INFO, "SoapySDR: device '%s' initialized\n", dev_data->device_string); + return 0; } -void *soapysdr_rx_thread(void *ctx) { - input_t *input = (input_t *)ctx; - soapysdr_dev_data_t *dev_data = (soapysdr_dev_data_t *)input->dev_data; - SoapySDRDevice *sdr = dev_data->dev; - assert(sdr != NULL); +void* soapysdr_rx_thread(void* ctx) { + input_t* input = (input_t*)ctx; + soapysdr_dev_data_t* dev_data = (soapysdr_dev_data_t*)input->dev_data; + SoapySDRDevice* sdr = dev_data->dev; + assert(sdr != NULL); - unsigned char buf[SOAPYSDR_BUFSIZE]; -// size of the buffer in number of I/Q sample pairs - size_t num_elems = SOAPYSDR_BUFSIZE / (2 * input->bytes_per_sample); + unsigned char buf[SOAPYSDR_BUFSIZE]; + // size of the buffer in number of I/Q sample pairs + size_t num_elems = SOAPYSDR_BUFSIZE / (2 * input->bytes_per_sample); - SoapySDRStream *rxStream = NULL; + SoapySDRStream* rxStream = NULL; #if SOAPY_SDR_API_VERSION < 0x00080000 - if(SoapySDRDevice_setupStream(sdr, &rxStream, SOAPY_SDR_RX, dev_data->sample_format, - &dev_data->channel, 1, NULL) != 0) { + if (SoapySDRDevice_setupStream(sdr, &rxStream, SOAPY_SDR_RX, dev_data->sample_format, &dev_data->channel, 1, NULL) != 0) { #else - if((rxStream = SoapySDRDevice_setupStream(sdr, SOAPY_SDR_RX, dev_data->sample_format, - &dev_data->channel, 1, NULL)) == NULL) { + if ((rxStream = SoapySDRDevice_setupStream(sdr, SOAPY_SDR_RX, dev_data->sample_format, &dev_data->channel, 1, NULL)) == NULL) { #endif /* SOAPY_SDR_API_VERSION */ - log(LOG_ERR, "Failed to set up stream for SoapySDR device '%s': %s\n", - dev_data->device_string, SoapySDRDevice_lastError()); - input->state = INPUT_FAILED; - goto cleanup; - } - if(SoapySDRDevice_activateStream(sdr, rxStream, 0, 0, 0)) { //start streaming - log(LOG_ERR, "Failed to activate stream for SoapySDR device '%s': %s\n", - dev_data->device_string, SoapySDRDevice_lastError()); - input->state = INPUT_FAILED; - goto cleanup; - } - input->state = INPUT_RUNNING; - log(LOG_NOTICE, "SoapySDR: device '%s' started\n", dev_data->device_string); + log(LOG_ERR, "Failed to set up stream for SoapySDR device '%s': %s\n", dev_data->device_string, SoapySDRDevice_lastError()); + input->state = INPUT_FAILED; + goto cleanup; + } + if (SoapySDRDevice_activateStream(sdr, rxStream, 0, 0, 0)) { // start streaming + log(LOG_ERR, "Failed to activate stream for SoapySDR device '%s': %s\n", dev_data->device_string, SoapySDRDevice_lastError()); + input->state = INPUT_FAILED; + goto cleanup; + } + input->state = INPUT_RUNNING; + log(LOG_NOTICE, "SoapySDR: device '%s' started\n", dev_data->device_string); - while(!do_exit) { - void *bufs[] = { buf }; // array of buffers - int flags; // flags set by receive operation - long long timeNs; // timestamp for receive buffer - int samples_read = SoapySDRDevice_readStream(sdr, rxStream, bufs, - num_elems, &flags, &timeNs, SOAPYSDR_READSTREAM_TIMEOUT_US); - if(samples_read < 0) { // when it's negative, it's the error code - log(LOG_ERR, "SoapySDR device '%s': readStream failed: %s\n", - dev_data->device_string, SoapySDR_errToStr(samples_read)); - continue; - } - circbuffer_append(input, buf, (size_t)(samples_read * 2 * input->bytes_per_sample)); - } + while (!do_exit) { + void* bufs[] = {buf}; // array of buffers + int flags; // flags set by receive operation + long long timeNs; // timestamp for receive buffer + int samples_read = SoapySDRDevice_readStream(sdr, rxStream, bufs, num_elems, &flags, &timeNs, SOAPYSDR_READSTREAM_TIMEOUT_US); + if (samples_read < 0) { // when it's negative, it's the error code + log(LOG_ERR, "SoapySDR device '%s': readStream failed: %s\n", dev_data->device_string, SoapySDR_errToStr(samples_read)); + continue; + } + circbuffer_append(input, buf, (size_t)(samples_read * 2 * input->bytes_per_sample)); + } cleanup: - SoapySDRDevice_deactivateStream(sdr, rxStream, 0, 0); - SoapySDRDevice_closeStream(sdr, rxStream); - SoapySDRDevice_unmake(sdr); - return 0; + SoapySDRDevice_deactivateStream(sdr, rxStream, 0, 0); + SoapySDRDevice_closeStream(sdr, rxStream); + SoapySDRDevice_unmake(sdr); + return 0; } -int soapysdr_set_centerfreq(input_t * const input, int const centerfreq) { - soapysdr_dev_data_t *dev_data = (soapysdr_dev_data_t *)input->dev_data; - assert(dev_data->dev != NULL); +int soapysdr_set_centerfreq(input_t* const input, int const centerfreq) { + soapysdr_dev_data_t* dev_data = (soapysdr_dev_data_t*)input->dev_data; + assert(dev_data->dev != NULL); - if(SoapySDRDevice_setFrequency(dev_data->dev, SOAPY_SDR_RX, dev_data->channel, centerfreq, NULL) != 0) { - log(LOG_ERR, "Failed to set frequency for SoapySDR device '%s': %s\n", - dev_data->device_string, SoapySDRDevice_lastError()); - return -1; - } - return 0; + if (SoapySDRDevice_setFrequency(dev_data->dev, SOAPY_SDR_RX, dev_data->channel, centerfreq, NULL) != 0) { + log(LOG_ERR, "Failed to set frequency for SoapySDR device '%s': %s\n", dev_data->device_string, SoapySDRDevice_lastError()); + return -1; + } + return 0; } -MODULE_EXPORT input_t *soapysdr_input_new() { - soapysdr_dev_data_t *dev_data = (soapysdr_dev_data_t *)XCALLOC(1, sizeof(soapysdr_dev_data_t)); - dev_data->gain = -1.0; // invalid default gain value - dev_data->agc = false; - memset(&dev_data->gains, 0, sizeof(dev_data->gains)); - dev_data->channel = 0; - dev_data->antenna = NULL; -/* return &( input_t ){ - .dev_data = dev_data, - .state = INPUT_UNKNOWN, - .sfmt = SFMT_U8, - .sample_rate = -1, - .parse_config = &soapysdr_parse_config, - .init = &soapysdr_init, - .run_rx_thread = &soapysdr_rx_thread, - .set_centerfreq = &soapysdr_set_centerfreq, - .stop = &soapysdr_stop - }; */ - input_t *input = (input_t *)XCALLOC(1, sizeof(input_t)); - input->dev_data = dev_data; -// invalid values as defaults - input->state = INPUT_UNKNOWN; - input->sfmt = SFMT_UNDEF; - input->fullscale = 0.0f; - input->bytes_per_sample = 0; - input->sample_rate = -1; +MODULE_EXPORT input_t* soapysdr_input_new() { + soapysdr_dev_data_t* dev_data = (soapysdr_dev_data_t*)XCALLOC(1, sizeof(soapysdr_dev_data_t)); + dev_data->gain = -1.0; // invalid default gain value + dev_data->agc = false; + memset(&dev_data->gains, 0, sizeof(dev_data->gains)); + dev_data->channel = 0; + dev_data->antenna = NULL; + /* return &( input_t ){ + .dev_data = dev_data, + .state = INPUT_UNKNOWN, + .sfmt = SFMT_U8, + .sample_rate = -1, + .parse_config = &soapysdr_parse_config, + .init = &soapysdr_init, + .run_rx_thread = &soapysdr_rx_thread, + .set_centerfreq = &soapysdr_set_centerfreq, + .stop = &soapysdr_stop + }; */ + input_t* input = (input_t*)XCALLOC(1, sizeof(input_t)); + input->dev_data = dev_data; + // invalid values as defaults + input->state = INPUT_UNKNOWN; + input->sfmt = SFMT_UNDEF; + input->fullscale = 0.0f; + input->bytes_per_sample = 0; + input->sample_rate = -1; - input->parse_config = &soapysdr_parse_config; - input->init = &soapysdr_init; - input->run_rx_thread = &soapysdr_rx_thread; - input->set_centerfreq = &soapysdr_set_centerfreq; - input->stop = NULL; - return input; + input->parse_config = &soapysdr_parse_config; + input->init = &soapysdr_init; + input->run_rx_thread = &soapysdr_rx_thread; + input->set_centerfreq = &soapysdr_set_centerfreq; + input->stop = NULL; + return input; } diff --git a/src/input-soapysdr.h b/src/input-soapysdr.h index aad6a25..775bde0 100644 --- a/src/input-soapysdr.h +++ b/src/input-soapysdr.h @@ -17,20 +17,20 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#include // SoapySDRDevice -#include // SoapySDRKwargs +#include // SoapySDRDevice +#include // SoapySDRKwargs #define SOAPYSDR_DEFAULT_SAMPLE_RATE 2560000 #define SOAPYSDR_BUFSIZE 320000 #define SOAPYSDR_READSTREAM_TIMEOUT_US 1000000L typedef struct { - SoapySDRDevice *dev; // pointer to device struct - char const *device_string; // SoapySDR device arg string - char const *sample_format; // sample format - char const *antenna; // antenna name - SoapySDRKwargs gains; // gain elements and their values - double correction; // PPM correction - double gain; // gain in dB - size_t channel; // HW channel number - bool agc; // enable AGC + SoapySDRDevice* dev; // pointer to device struct + char const* device_string; // SoapySDR device arg string + char const* sample_format; // sample format + char const* antenna; // antenna name + SoapySDRKwargs gains; // gain elements and their values + double correction; // PPM correction + double gain; // gain in dB + size_t channel; // HW channel number + bool agc; // enable AGC } soapysdr_dev_data_t; diff --git a/src/logging.cpp b/src/logging.cpp index 89f63b7..2933519 100644 --- a/src/logging.cpp +++ b/src/logging.cpp @@ -18,55 +18,54 @@ * along with this program. If not, see . */ -#include // fopen() -#include // strerror() -#include // cerr() -#include // va_start() / va_end() +#include // va_start() / va_end() +#include // fopen() +#include // strerror() +#include // cerr() #include "logging.h" LogDestination log_destination = SYSLOG; -FILE *debugf = NULL; +FILE* debugf = NULL; void error() { - close_debug(); - _Exit(1); + close_debug(); + _Exit(1); } -void init_debug (const char *file) { +void init_debug(const char* file) { #ifdef DEBUG - if(!file) return; - if((debugf = fopen(file, "a")) == NULL) { - - std::cerr << "Could not open debug file " << file << ": " << strerror(errno) << "\n"; - error(); - } + if (!file) + return; + if ((debugf = fopen(file, "a")) == NULL) { + std::cerr << "Could not open debug file " << file << ": " << strerror(errno) << "\n"; + error(); + } #else - UNUSED(file); + UNUSED(file); #endif /* DEBUG */ } void close_debug() { #ifdef DEBUG - if(!debugf) return; - fclose(debugf); + if (!debugf) + return; + fclose(debugf); #endif /* DEBUG */ } -void log(int priority, const char *format, ...) { - va_list args; - va_start(args, format); - switch (log_destination) - { - case SYSLOG: - vsyslog(priority, format, args); - break; - case STDERR: - vfprintf(stderr, format, args); - break; - case NONE: - break; - } - va_end(args); +void log(int priority, const char* format, ...) { + va_list args; + va_start(args, format); + switch (log_destination) { + case SYSLOG: + vsyslog(priority, format, args); + break; + case STDERR: + vfprintf(stderr, format, args); + break; + case NONE: + break; + } + va_end(args); } - diff --git a/src/logging.h b/src/logging.h index 0bf5feb..87f7ddb 100644 --- a/src/logging.h +++ b/src/logging.h @@ -21,31 +21,37 @@ #ifndef _LOGGING_H #define _LOGGING_H 1 -#include // FILE -#include // LOG_ERR +#include // LOG_ERR +#include // FILE -#define nop() do {} while (0) +#define nop() \ + do { \ + } while (0) #define UNUSED(x) (void)(x) #ifdef DEBUG #define DEBUG_PATH "rtl_airband_debug.log" -#define debug_print(fmt, ...) \ - do { fprintf(debugf, "%s(): " fmt, __func__, __VA_ARGS__); fflush(debugf); } while (0) -#define debug_bulk_print(fmt, ...) \ - do { fprintf(debugf, "%s(): " fmt, __func__, __VA_ARGS__); } while (0) +#define debug_print(fmt, ...) \ + do { \ + fprintf(debugf, "%s(): " fmt, __func__, __VA_ARGS__); \ + fflush(debugf); \ + } while (0) +#define debug_bulk_print(fmt, ...) \ + do { \ + fprintf(debugf, "%s(): " fmt, __func__, __VA_ARGS__); \ + } while (0) #else #define debug_print(fmt, ...) nop() #define debug_bulk_print(fmt, ...) nop() #endif /* DEBUG */ -enum LogDestination { SYSLOG, STDERR, NONE}; +enum LogDestination { SYSLOG, STDERR, NONE }; extern LogDestination log_destination; -extern FILE *debugf; +extern FILE* debugf; void error(); -void init_debug(const char *file); +void init_debug(const char* file); void close_debug(); -void log(int priority, const char *format, ...); +void log(int priority, const char* format, ...); #endif /* _LOGGING_H */ - diff --git a/src/mixer.cpp b/src/mixer.cpp index 2266ec1..817ff02 100644 --- a/src/mixer.cpp +++ b/src/mixer.cpp @@ -18,125 +18,125 @@ * along with this program. If not, see . */ -#include -#include -#include -#include #include #include #include -#include "rtl_airband.h" +#include +#include +#include +#include #include "config.h" +#include "rtl_airband.h" -static char *err; +static char* err; -static inline void mixer_set_error(const char *msg) { - err = strdup(msg); +static inline void mixer_set_error(const char* msg) { + err = strdup(msg); } -const char *mixer_get_error() { - return (const char *)err; +const char* mixer_get_error() { + return (const char*)err; } -mixer_t *getmixerbyname(const char *name) { - for(int i = 0; i < mixer_count; i++) { - if(!strcmp(mixers[i].name, name)) { - debug_print("%s found at %d\n", name, i); - return &mixers[i]; - } - } - debug_print("%s not found\n", name); - return NULL; +mixer_t* getmixerbyname(const char* name) { + for (int i = 0; i < mixer_count; i++) { + if (!strcmp(mixers[i].name, name)) { + debug_print("%s found at %d\n", name, i); + return &mixers[i]; + } + } + debug_print("%s not found\n", name); + return NULL; } -void mixer_disable(mixer_t *mixer) { - mixer->enabled = false; - disable_channel_outputs(&mixer->channel); +void mixer_disable(mixer_t* mixer) { + mixer->enabled = false; + disable_channel_outputs(&mixer->channel); } -int mixer_connect_input(mixer_t *mixer, float ampfactor, float balance) { - if(!mixer) { - mixer_set_error("mixer is undefined"); - return(-1); - } - int i = mixer->input_count; +int mixer_connect_input(mixer_t* mixer, float ampfactor, float balance) { + if (!mixer) { + mixer_set_error("mixer is undefined"); + return (-1); + } + int i = mixer->input_count; - // allocate new mixer - this could be more efficient by pre-allocating but this - // is only run at startup so not a big deal - if (mixer->inputs == NULL) { - mixer->inputs = (mixinput_t *)XCALLOC(i+1, sizeof(struct mixinput_t)); - mixer->inputs_todo = (bool *)XCALLOC(i+1, sizeof(bool)); - mixer->input_mask = (bool *)XCALLOC(i+1, sizeof(bool)); - } else { - mixer->inputs = (mixinput_t *)XREALLOC(mixer->inputs, (i+1) * sizeof(struct mixinput_t)); - mixer->inputs_todo = (bool *)XREALLOC(mixer->inputs_todo, (i+1) * sizeof(bool)); - mixer->input_mask = (bool *)XREALLOC(mixer->input_mask, (i+1) * sizeof(bool)); - } + // allocate new mixer - this could be more efficient by pre-allocating but this + // is only run at startup so not a big deal + if (mixer->inputs == NULL) { + mixer->inputs = (mixinput_t*)XCALLOC(i + 1, sizeof(struct mixinput_t)); + mixer->inputs_todo = (bool*)XCALLOC(i + 1, sizeof(bool)); + mixer->input_mask = (bool*)XCALLOC(i + 1, sizeof(bool)); + } else { + mixer->inputs = (mixinput_t*)XREALLOC(mixer->inputs, (i + 1) * sizeof(struct mixinput_t)); + mixer->inputs_todo = (bool*)XREALLOC(mixer->inputs_todo, (i + 1) * sizeof(bool)); + mixer->input_mask = (bool*)XREALLOC(mixer->input_mask, (i + 1) * sizeof(bool)); + } - mixer->inputs[i].wavein = (float *)XCALLOC(WAVE_LEN, sizeof(float)); - if((pthread_mutex_init(&mixer->inputs[i].mutex, NULL)) != 0) { - mixer_set_error("failed to initialize input mutex"); - return(-1); - } - mixer->inputs[i].ampfactor = ampfactor; - mixer->inputs[i].ampl = fminf(1.0f, 1.0f - balance); - mixer->inputs[i].ampr = fminf(1.0f, 1.0f + balance); - if(balance != 0.0f) - mixer->channel.mode = MM_STEREO; - mixer->inputs[i].ready = false; - mixer->inputs[i].has_signal = false; - mixer->inputs[i].input_overrun_count = 0; - mixer->input_mask[i] = true; - mixer->inputs_todo[i] = true; - mixer->enabled = true; - debug_print("ampfactor=%.1f ampl=%.1f ampr=%.1f\n", mixer->inputs[i].ampfactor, mixer->inputs[i].ampl, mixer->inputs[i].ampr); - return(mixer->input_count++); + mixer->inputs[i].wavein = (float*)XCALLOC(WAVE_LEN, sizeof(float)); + if ((pthread_mutex_init(&mixer->inputs[i].mutex, NULL)) != 0) { + mixer_set_error("failed to initialize input mutex"); + return (-1); + } + mixer->inputs[i].ampfactor = ampfactor; + mixer->inputs[i].ampl = fminf(1.0f, 1.0f - balance); + mixer->inputs[i].ampr = fminf(1.0f, 1.0f + balance); + if (balance != 0.0f) + mixer->channel.mode = MM_STEREO; + mixer->inputs[i].ready = false; + mixer->inputs[i].has_signal = false; + mixer->inputs[i].input_overrun_count = 0; + mixer->input_mask[i] = true; + mixer->inputs_todo[i] = true; + mixer->enabled = true; + debug_print("ampfactor=%.1f ampl=%.1f ampr=%.1f\n", mixer->inputs[i].ampfactor, mixer->inputs[i].ampl, mixer->inputs[i].ampr); + return (mixer->input_count++); } -void mixer_disable_input(mixer_t *mixer, int input_idx) { - assert(mixer); - assert(input_idx < mixer->input_count); +void mixer_disable_input(mixer_t* mixer, int input_idx) { + assert(mixer); + assert(input_idx < mixer->input_count); - mixer->input_mask[input_idx] = false; + mixer->input_mask[input_idx] = false; - // break out if any inputs remain true - for (int i = 0; i < mixer->input_count; i++) { - if (mixer->input_mask[i]) { - return; - } - } + // break out if any inputs remain true + for (int i = 0; i < mixer->input_count; i++) { + if (mixer->input_mask[i]) { + return; + } + } - // all inputs are false so disable the mixer - log(LOG_NOTICE, "Disabling mixer '%s' - all inputs died\n", mixer->name); - mixer_disable(mixer); + // all inputs are false so disable the mixer + log(LOG_NOTICE, "Disabling mixer '%s' - all inputs died\n", mixer->name); + mixer_disable(mixer); } -void mixer_put_samples(mixer_t *mixer, int input_idx, const float *samples, bool has_signal, unsigned int len) { - assert(mixer); - assert(samples); - assert(input_idx < mixer->input_count); - mixinput_t *input = &mixer->inputs[input_idx]; - pthread_mutex_lock(&input->mutex); - input->has_signal = has_signal; - if (has_signal) { - memcpy(input->wavein, samples, len * sizeof(float)); - } - if(input->ready == true) { - debug_print("input %d overrun\n", input_idx); - input->input_overrun_count++; - } else { - input->ready = true; - } - pthread_mutex_unlock(&input->mutex); +void mixer_put_samples(mixer_t* mixer, int input_idx, const float* samples, bool has_signal, unsigned int len) { + assert(mixer); + assert(samples); + assert(input_idx < mixer->input_count); + mixinput_t* input = &mixer->inputs[input_idx]; + pthread_mutex_lock(&input->mutex); + input->has_signal = has_signal; + if (has_signal) { + memcpy(input->wavein, samples, len * sizeof(float)); + } + if (input->ready == true) { + debug_print("input %d overrun\n", input_idx); + input->input_overrun_count++; + } else { + input->ready = true; + } + pthread_mutex_unlock(&input->mutex); } -void mix_waveforms(float *sum, const float *in, float mult, int size) { - if (mult == 0.0f) { - return; - } - for(int s = 0; s < size; s++) { - sum[s] += in[s] * mult; - } +void mix_waveforms(float* sum, const float* in, float mult, int size) { + if (mult == 0.0f) { + return; + } + for (int s = 0; s < size; s++) { + sum[s] += in[s] * mult; + } } /* Samples are delivered to mixer inputs in batches of WAVE_BATCH size (default 1000, ie. 1/8 secs @@ -154,108 +154,110 @@ void mix_waveforms(float *sum, const float *in, float mult, int size) { * interval. Any input which is still not ready, is skipped (filled with 0s), because * here we must emit the mixed audio to keep the desired audio bitrate. */ -void *mixer_thread(void *param) { - assert(param != NULL); - Signal *signal = (Signal *)param; - int interval_usec = 1e+6 * WAVE_BATCH / WAVE_RATE / MIX_DIVISOR; +void* mixer_thread(void* param) { + assert(param != NULL); + Signal* signal = (Signal*)param; + int interval_usec = 1e+6 * WAVE_BATCH / WAVE_RATE / MIX_DIVISOR; - debug_print("Starting mixer thread, signal %p\n", signal); + debug_print("Starting mixer thread, signal %p\n", signal); - if(mixer_count <= 0) return 0; + if (mixer_count <= 0) + return 0; #ifdef DEBUG - struct timeval ts, te; - gettimeofday(&ts, NULL); + struct timeval ts, te; + gettimeofday(&ts, NULL); #endif /* DEBUG */ - while(!do_exit) { - usleep(interval_usec); - if(do_exit) return 0; - for(int i = 0; i < mixer_count; i++) { - mixer_t *mixer = mixers + i; - if(mixer->enabled == false) continue; - channel_t *channel = &mixer->channel; + while (!do_exit) { + usleep(interval_usec); + if (do_exit) + return 0; + for (int i = 0; i < mixer_count; i++) { + mixer_t* mixer = mixers + i; + if (mixer->enabled == false) + continue; + channel_t* channel = &mixer->channel; - if(channel->state == CH_READY) { // previous output not yet handled by output thread - if(--mixer->interval > 0) { - continue; - } else { - debug_print("mixer[%d]: output channel overrun\n", i); - mixer->output_overrun_count++; - } - } + if (channel->state == CH_READY) { // previous output not yet handled by output thread + if (--mixer->interval > 0) { + continue; + } else { + debug_print("mixer[%d]: output channel overrun\n", i); + mixer->output_overrun_count++; + } + } - for(int j = 0; j < mixer->input_count; j++) { - mixinput_t *input = mixer->inputs + j; - pthread_mutex_lock(&input->mutex); - if(mixer->inputs_todo[j] && mixer->input_mask[j] && input->ready) { - if(channel->state == CH_DIRTY) { - memset(channel->waveout, 0, WAVE_BATCH * sizeof(float)); - if(channel->mode == MM_STEREO) - memset(channel->waveout_r, 0, WAVE_BATCH * sizeof(float)); - channel->axcindicate = NO_SIGNAL; - channel->state = CH_WORKING; - } - debug_bulk_print("mixer[%d]: ampleft=%.1f ampright=%.1f\n", i, input->ampfactor * input->ampl, input->ampfactor * input->ampr); - if(input->has_signal) { - /* left channel */ - mix_waveforms(channel->waveout, input->wavein, input->ampfactor * input->ampl, WAVE_BATCH); - /* right channel */ - if(channel->mode == MM_STEREO) { - mix_waveforms(channel->waveout_r, input->wavein, input->ampfactor * input->ampr, WAVE_BATCH); - } - channel->axcindicate = SIGNAL; - } - input->ready = false; - mixer->inputs_todo[j] = false; - } - pthread_mutex_unlock(&input->mutex); - } + for (int j = 0; j < mixer->input_count; j++) { + mixinput_t* input = mixer->inputs + j; + pthread_mutex_lock(&input->mutex); + if (mixer->inputs_todo[j] && mixer->input_mask[j] && input->ready) { + if (channel->state == CH_DIRTY) { + memset(channel->waveout, 0, WAVE_BATCH * sizeof(float)); + if (channel->mode == MM_STEREO) + memset(channel->waveout_r, 0, WAVE_BATCH * sizeof(float)); + channel->axcindicate = NO_SIGNAL; + channel->state = CH_WORKING; + } + debug_bulk_print("mixer[%d]: ampleft=%.1f ampright=%.1f\n", i, input->ampfactor * input->ampl, input->ampfactor * input->ampr); + if (input->has_signal) { + /* left channel */ + mix_waveforms(channel->waveout, input->wavein, input->ampfactor * input->ampl, WAVE_BATCH); + /* right channel */ + if (channel->mode == MM_STEREO) { + mix_waveforms(channel->waveout_r, input->wavein, input->ampfactor * input->ampr, WAVE_BATCH); + } + channel->axcindicate = SIGNAL; + } + input->ready = false; + mixer->inputs_todo[j] = false; + } + pthread_mutex_unlock(&input->mutex); + } - // check if all "good" inputs have been handled. this means there is no enabled mixer (mixer->input_mask is true) that has a - // input to handle (mixer->inputs_todo is true) - bool all_good_inputs_handled = true; - for(int k = 0; k < mixer->input_count && all_good_inputs_handled; k++) { - if (mixer->inputs_todo[k] && mixer->input_mask[k]) { - all_good_inputs_handled = false; - } - } + // check if all "good" inputs have been handled. this means there is no enabled mixer (mixer->input_mask is true) that has a + // input to handle (mixer->inputs_todo is true) + bool all_good_inputs_handled = true; + for (int k = 0; k < mixer->input_count && all_good_inputs_handled; k++) { + if (mixer->inputs_todo[k] && mixer->input_mask[k]) { + all_good_inputs_handled = false; + } + } - if((all_good_inputs_handled == true) || mixer->interval == 0) { // all good inputs handled or last interval passed + if ((all_good_inputs_handled == true) || mixer->interval == 0) { // all good inputs handled or last interval passed #ifdef DEBUG - gettimeofday(&te, NULL); + gettimeofday(&te, NULL); - char *inputs_todo_char = (char*)XCALLOC(mixer->input_count+1, sizeof(char)); - char *input_mask_char = (char*)XCALLOC(mixer->input_count+1, sizeof(char)); - for(int k = 0; k < mixer->input_count; k++) { - inputs_todo_char[k] = mixer->inputs_todo[k] ? '+' : '-'; - input_mask_char[k] = mixer->input_mask[k] ? '+' : '-'; - } - inputs_todo_char[mixer->input_count] = '\0'; - input_mask_char[mixer->input_count] ='\0'; + char* inputs_todo_char = (char*)XCALLOC(mixer->input_count + 1, sizeof(char)); + char* input_mask_char = (char*)XCALLOC(mixer->input_count + 1, sizeof(char)); + for (int k = 0; k < mixer->input_count; k++) { + inputs_todo_char[k] = mixer->inputs_todo[k] ? '+' : '-'; + input_mask_char[k] = mixer->input_mask[k] ? '+' : '-'; + } + inputs_todo_char[mixer->input_count] = '\0'; + input_mask_char[mixer->input_count] = '\0'; - debug_bulk_print("mixerinput: %lu.%lu %lu int=%d inp_unhandled=%s inp_mask=%s\n", - te.tv_sec, (unsigned long) te.tv_usec, (te.tv_sec - ts.tv_sec) * 1000000UL + te.tv_usec - ts.tv_usec, - mixer->interval, inputs_todo_char, input_mask_char); + debug_bulk_print("mixerinput: %lu.%lu %lu int=%d inp_unhandled=%s inp_mask=%s\n", te.tv_sec, (unsigned long)te.tv_usec, (te.tv_sec - ts.tv_sec) * 1000000UL + te.tv_usec - ts.tv_usec, + mixer->interval, inputs_todo_char, input_mask_char); - free(inputs_todo_char); - free(input_mask_char); + free(inputs_todo_char); + free(input_mask_char); - ts.tv_sec = te.tv_sec; - ts.tv_usec = te.tv_usec; + ts.tv_sec = te.tv_sec; + ts.tv_usec = te.tv_usec; #endif /* DEBUG */ - channel->state = CH_READY; - signal->send(); - mixer->interval = MIX_DIVISOR; - for(int k = 0; k < mixer->input_count; k++) { - mixer->inputs_todo[k] = true; - } - } else { - mixer->interval--; - } - } - } - return 0; + channel->state = CH_READY; + signal->send(); + mixer->interval = MIX_DIVISOR; + for (int k = 0; k < mixer->input_count; k++) { + mixer->inputs_todo[k] = true; + } + } else { + mixer->interval--; + } + } + } + return 0; } // vim: ts=4 diff --git a/src/output.cpp b/src/output.cpp index 31d9134..c97a8dd 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -18,16 +18,15 @@ * along with this program. If not, see . */ +#include +#include +#include #include -#include -#include #include #include +#include #include -#include -#include #include -#include // SHOUTERR_RETRY is available since libshout 2.4.0. // Set it to an impossible value if it's not there. @@ -42,202 +41,206 @@ #endif /* WITH_PULSEAUDIO */ #include +#include +#include #include #include #include -#include -#include -#include #include -#include "rtl_airband.h" -#include "input-common.h" +#include #include "config.h" #include "helper_functions.h" +#include "input-common.h" +#include "rtl_airband.h" -void shout_setup(icecast_data *icecast, mix_modes mixmode) { - int ret; - shout_t * shouttemp = shout_new(); - if (shouttemp == NULL) { - printf("cannot allocate\n"); - } - if (shout_set_host(shouttemp, icecast->hostname) != SHOUTERR_SUCCESS) { - shout_free(shouttemp); return; - } - if (shout_set_protocol(shouttemp, SHOUT_PROTOCOL_HTTP) != SHOUTERR_SUCCESS) { - shout_free(shouttemp); return; - } - if (shout_set_port(shouttemp, icecast->port) != SHOUTERR_SUCCESS) { - shout_free(shouttemp); return; - } +void shout_setup(icecast_data* icecast, mix_modes mixmode) { + int ret; + shout_t* shouttemp = shout_new(); + if (shouttemp == NULL) { + printf("cannot allocate\n"); + } + if (shout_set_host(shouttemp, icecast->hostname) != SHOUTERR_SUCCESS) { + shout_free(shouttemp); + return; + } + if (shout_set_protocol(shouttemp, SHOUT_PROTOCOL_HTTP) != SHOUTERR_SUCCESS) { + shout_free(shouttemp); + return; + } + if (shout_set_port(shouttemp, icecast->port) != SHOUTERR_SUCCESS) { + shout_free(shouttemp); + return; + } #ifdef LIBSHOUT_HAS_TLS - if (shout_set_tls(shouttemp, icecast->tls_mode) != SHOUTERR_SUCCESS) { - shout_free(shouttemp); return; - } + if (shout_set_tls(shouttemp, icecast->tls_mode) != SHOUTERR_SUCCESS) { + shout_free(shouttemp); + return; + } #endif /* LIBSHOUT_HAS_TLS */ - char mp[100]; - sprintf(mp, "/%s", icecast->mountpoint); - if (shout_set_mount(shouttemp, mp) != SHOUTERR_SUCCESS) { - shout_free(shouttemp); return; - } - if (shout_set_user(shouttemp, icecast->username) != SHOUTERR_SUCCESS) { - shout_free(shouttemp); return; - } - if (shout_set_password(shouttemp, icecast->password) != SHOUTERR_SUCCESS) { - shout_free(shouttemp); return; - } + char mp[100]; + sprintf(mp, "/%s", icecast->mountpoint); + if (shout_set_mount(shouttemp, mp) != SHOUTERR_SUCCESS) { + shout_free(shouttemp); + return; + } + if (shout_set_user(shouttemp, icecast->username) != SHOUTERR_SUCCESS) { + shout_free(shouttemp); + return; + } + if (shout_set_password(shouttemp, icecast->password) != SHOUTERR_SUCCESS) { + shout_free(shouttemp); + return; + } #ifdef LIBSHOUT_HAS_CONTENT_FORMAT - if (shout_set_content_format(shouttemp, SHOUT_FORMAT_MP3, SHOUT_USAGE_AUDIO, NULL) != SHOUTERR_SUCCESS){ + if (shout_set_content_format(shouttemp, SHOUT_FORMAT_MP3, SHOUT_USAGE_AUDIO, NULL) != SHOUTERR_SUCCESS) { #else - if (shout_set_format(shouttemp, SHOUT_FORMAT_MP3) != SHOUTERR_SUCCESS){ + if (shout_set_format(shouttemp, SHOUT_FORMAT_MP3) != SHOUTERR_SUCCESS) { #endif /* LIBSHOUT_HAS_CONTENT_FORMAT */ - shout_free(shouttemp); return; - } - if(icecast->name && shout_set_meta(shouttemp, SHOUT_META_NAME, icecast->name) != SHOUTERR_SUCCESS) { - shout_free(shouttemp); return; - } - if(icecast->genre && shout_set_meta(shouttemp, SHOUT_META_GENRE, icecast->genre) != SHOUTERR_SUCCESS) { - shout_free(shouttemp); return; - } - if(icecast->description && shout_set_meta(shouttemp, SHOUT_META_DESCRIPTION, icecast->description) != SHOUTERR_SUCCESS) { - shout_free(shouttemp); return; - } - char samplerates[20]; - sprintf(samplerates, "%d", MP3_RATE); - shout_set_audio_info(shouttemp, SHOUT_AI_SAMPLERATE, samplerates); - shout_set_audio_info(shouttemp, SHOUT_AI_CHANNELS, (mixmode == MM_STEREO ? "2" : "1")); - - if (shout_set_nonblocking(shouttemp, 1) != SHOUTERR_SUCCESS) { - log(LOG_ERR, "Error setting non-blocking mode: %s\n", shout_get_error(shouttemp)); - return; - } - ret = shout_open(shouttemp); - if (ret == SHOUTERR_SUCCESS) - ret = SHOUTERR_CONNECTED; - - if (ret == SHOUTERR_BUSY || ret == SHOUTERR_RETRY) - log(LOG_NOTICE, "Connecting to %s:%d/%s...\n", - icecast->hostname, icecast->port, icecast->mountpoint); - - int shout_timeout = 30 * 5; // 30 * 5 * 200ms = 30s - while ((ret == SHOUTERR_BUSY || ret == SHOUTERR_RETRY) && shout_timeout-- > 0) { - SLEEP(200); - ret = shout_get_connected(shouttemp); - } - - if (ret == SHOUTERR_CONNECTED) { - log(LOG_NOTICE, "Connected to %s:%d/%s\n", - icecast->hostname, icecast->port, icecast->mountpoint); - SLEEP(100); - icecast->shout = shouttemp; - } else { - log(LOG_WARNING, "Could not connect to %s:%d/%s: %s\n", - icecast->hostname, icecast->port, icecast->mountpoint, shout_get_error(shouttemp)); - shout_close(shouttemp); - shout_free(shouttemp); - return; - } + shout_free(shouttemp); + return; + } + if (icecast->name && shout_set_meta(shouttemp, SHOUT_META_NAME, icecast->name) != SHOUTERR_SUCCESS) { + shout_free(shouttemp); + return; + } + if (icecast->genre && shout_set_meta(shouttemp, SHOUT_META_GENRE, icecast->genre) != SHOUTERR_SUCCESS) { + shout_free(shouttemp); + return; + } + if (icecast->description && shout_set_meta(shouttemp, SHOUT_META_DESCRIPTION, icecast->description) != SHOUTERR_SUCCESS) { + shout_free(shouttemp); + return; + } + char samplerates[20]; + sprintf(samplerates, "%d", MP3_RATE); + shout_set_audio_info(shouttemp, SHOUT_AI_SAMPLERATE, samplerates); + shout_set_audio_info(shouttemp, SHOUT_AI_CHANNELS, (mixmode == MM_STEREO ? "2" : "1")); + + if (shout_set_nonblocking(shouttemp, 1) != SHOUTERR_SUCCESS) { + log(LOG_ERR, "Error setting non-blocking mode: %s\n", shout_get_error(shouttemp)); + return; + } + ret = shout_open(shouttemp); + if (ret == SHOUTERR_SUCCESS) + ret = SHOUTERR_CONNECTED; + + if (ret == SHOUTERR_BUSY || ret == SHOUTERR_RETRY) + log(LOG_NOTICE, "Connecting to %s:%d/%s...\n", icecast->hostname, icecast->port, icecast->mountpoint); + + int shout_timeout = 30 * 5; // 30 * 5 * 200ms = 30s + while ((ret == SHOUTERR_BUSY || ret == SHOUTERR_RETRY) && shout_timeout-- > 0) { + SLEEP(200); + ret = shout_get_connected(shouttemp); + } + + if (ret == SHOUTERR_CONNECTED) { + log(LOG_NOTICE, "Connected to %s:%d/%s\n", icecast->hostname, icecast->port, icecast->mountpoint); + SLEEP(100); + icecast->shout = shouttemp; + } else { + log(LOG_WARNING, "Could not connect to %s:%d/%s: %s\n", icecast->hostname, icecast->port, icecast->mountpoint, shout_get_error(shouttemp)); + shout_close(shouttemp); + shout_free(shouttemp); + return; + } } lame_t airlame_init(mix_modes mixmode, int highpass, int lowpass) { - lame_t lame = lame_init(); - if (!lame) { - log(LOG_WARNING, "lame_init failed\n"); - return NULL; - } - - lame_set_in_samplerate(lame, WAVE_RATE); - lame_set_VBR(lame, vbr_mtrh); - lame_set_brate(lame, 16); - lame_set_quality(lame, 7); - lame_set_lowpassfreq(lame, lowpass); - lame_set_highpassfreq(lame, highpass); - lame_set_out_samplerate(lame, MP3_RATE); - if(mixmode == MM_STEREO) { - lame_set_num_channels(lame, 2); - lame_set_mode(lame, JOINT_STEREO); - } else { - lame_set_num_channels(lame, 1); - lame_set_mode(lame, MONO); - } - debug_print("lame init with mixmode=%s\n", mixmode == MM_STEREO ? "MM_STEREO" : "MM_MONO"); - lame_init_params(lame); - return lame; + lame_t lame = lame_init(); + if (!lame) { + log(LOG_WARNING, "lame_init failed\n"); + return NULL; + } + + lame_set_in_samplerate(lame, WAVE_RATE); + lame_set_VBR(lame, vbr_mtrh); + lame_set_brate(lame, 16); + lame_set_quality(lame, 7); + lame_set_lowpassfreq(lame, lowpass); + lame_set_highpassfreq(lame, highpass); + lame_set_out_samplerate(lame, MP3_RATE); + if (mixmode == MM_STEREO) { + lame_set_num_channels(lame, 2); + lame_set_mode(lame, JOINT_STEREO); + } else { + lame_set_num_channels(lame, 1); + lame_set_mode(lame, MONO); + } + debug_print("lame init with mixmode=%s\n", mixmode == MM_STEREO ? "MM_STEREO" : "MM_MONO"); + lame_init_params(lame); + return lame; } -class LameTone -{ - unsigned char* _data; - int _bytes; - -public: - LameTone(mix_modes mixmode, int msec, unsigned int hz = 0) : _data(NULL), _bytes(0) { - _data = (unsigned char *)XCALLOC(1, LAMEBUF_SIZE); - - int samples = (msec * WAVE_RATE) / 1000; - float *buf = (float *)XCALLOC(samples, sizeof(float)); - - debug_print("LameTone with mixmode=%s msec=%d hz=%u\n", - mixmode == MM_STEREO ? "MM_STEREO" : "MM_MONO", - msec, hz); - if (hz > 0) { - const float period = 1.0 / (float)hz; - const float sample_time = 1.0 / (float)WAVE_RATE; - float t = 0; - for (int i = 0; i < samples; ++i, t+= sample_time) { - buf[i] = 0.9 * sinf(t * 2.0 * M_PI / period); - } - } else - memset(buf, 0, samples * sizeof(float)); - lame_t lame = airlame_init(mixmode, 0, 0); - if (lame) { - _bytes = lame_encode_buffer_ieee_float(lame, buf, (mixmode == MM_STEREO ? buf : NULL), samples, _data, LAMEBUF_SIZE); - if (_bytes > 0) { - int flush_ofs = _bytes; - if (flush_ofs&0x1f) - flush_ofs+= 0x20 - (flush_ofs&0x1f); - if (flush_ofs < LAMEBUF_SIZE) { - int flush_bytes = lame_encode_flush(lame, _data + flush_ofs, LAMEBUF_SIZE - flush_ofs); - if (flush_bytes > 0) { - memmove(_data + _bytes, _data + flush_ofs, flush_bytes); - _bytes+= flush_bytes; - } - } - } - else - log(LOG_WARNING, "lame_encode_buffer_ieee_float: %d\n", _bytes); - lame_close(lame); - } - free(buf); - } - - ~LameTone() { - if (_data) - free(_data); - } - - int write(FILE *f) { - if (!_data || _bytes<=0) - return 1; - - if (fwrite(_data, 1, _bytes, f) != (unsigned int)_bytes) { - log(LOG_WARNING, "LameTone: failed to write %d bytes\n", _bytes); - return -1; - } - - return 0; - } +class LameTone { + unsigned char* _data; + int _bytes; + + public: + LameTone(mix_modes mixmode, int msec, unsigned int hz = 0) : _data(NULL), _bytes(0) { + _data = (unsigned char*)XCALLOC(1, LAMEBUF_SIZE); + + int samples = (msec * WAVE_RATE) / 1000; + float* buf = (float*)XCALLOC(samples, sizeof(float)); + + debug_print("LameTone with mixmode=%s msec=%d hz=%u\n", mixmode == MM_STEREO ? "MM_STEREO" : "MM_MONO", msec, hz); + if (hz > 0) { + const float period = 1.0 / (float)hz; + const float sample_time = 1.0 / (float)WAVE_RATE; + float t = 0; + for (int i = 0; i < samples; ++i, t += sample_time) { + buf[i] = 0.9 * sinf(t * 2.0 * M_PI / period); + } + } else + memset(buf, 0, samples * sizeof(float)); + lame_t lame = airlame_init(mixmode, 0, 0); + if (lame) { + _bytes = lame_encode_buffer_ieee_float(lame, buf, (mixmode == MM_STEREO ? buf : NULL), samples, _data, LAMEBUF_SIZE); + if (_bytes > 0) { + int flush_ofs = _bytes; + if (flush_ofs & 0x1f) + flush_ofs += 0x20 - (flush_ofs & 0x1f); + if (flush_ofs < LAMEBUF_SIZE) { + int flush_bytes = lame_encode_flush(lame, _data + flush_ofs, LAMEBUF_SIZE - flush_ofs); + if (flush_bytes > 0) { + memmove(_data + _bytes, _data + flush_ofs, flush_bytes); + _bytes += flush_bytes; + } + } + } else + log(LOG_WARNING, "lame_encode_buffer_ieee_float: %d\n", _bytes); + lame_close(lame); + } + free(buf); + } + + ~LameTone() { + if (_data) + free(_data); + } + + int write(FILE* f) { + if (!_data || _bytes <= 0) + return 1; + + if (fwrite(_data, 1, _bytes, f) != (unsigned int)_bytes) { + log(LOG_WARNING, "LameTone: failed to write %d bytes\n", _bytes); + return -1; + } + + return 0; + } }; -int rename_if_exists(char const *oldpath, char const *newpath) { - int ret = rename(oldpath, newpath); - if(ret < 0) { - if(errno == ENOENT) { - return 0; - } else { - log(LOG_ERR, "Could not rename %s to %s: %s\n", oldpath, newpath, strerror(errno)); - } - } - return ret; +int rename_if_exists(char const* oldpath, char const* newpath) { + int ret = rename(oldpath, newpath); + if (ret < 0) { + if (errno == ENOENT) { + return 0; + } else { + log(LOG_ERR, "Could not rename %s to %s: %s\n", oldpath, newpath, strerror(errno)); + } + } + return ret; } /* @@ -245,91 +248,93 @@ int rename_if_exists(char const *oldpath, char const *newpath) { * If appending to an audio file, insert discontinuity indictor tones * as well as the appropriate amount of silence when in continuous mode. */ -static int open_file(file_data *fdata, mix_modes mixmode, int is_audio) { - int rename_result = rename_if_exists(fdata->file_path.c_str(), fdata->file_path_tmp.c_str()); - fdata->f = fopen(fdata->file_path_tmp.c_str(), fdata->append ? "a+" : "w"); - if (fdata->f == NULL) { - return -1; - } - - struct stat st = {}; - if (!fdata->append || - fstat(fileno(fdata->f), &st) != 0 || st.st_size == 0) { - if(!fdata->split_on_transmission) { - log(LOG_INFO, "Writing to %s\n", fdata->file_path.c_str()); - } else { - debug_print("Writing to %s\n", fdata->file_path_tmp.c_str()); - } - return 0; - } - if(rename_result < 0) { - log(LOG_INFO, "Writing to %s\n", fdata->file_path.c_str()); - debug_print("Writing to %s\n", fdata->file_path_tmp.c_str()); - } else { - log(LOG_INFO, "Appending from pos %llu to %s\n", - (unsigned long long)st.st_size, fdata->file_path.c_str()); - debug_print("Appending from pos %llu to %s\n", - (unsigned long long)st.st_size, fdata->file_path_tmp.c_str()); - } - - if (is_audio) { - // fill missing space with marker tones - LameTone lt_a(mixmode, 120, 2222); - LameTone lt_b(mixmode, 120, 1111); - LameTone lt_c(mixmode, 120, 555); - - int r = lt_a.write(fdata->f); - if (r==0) r = lt_b.write(fdata->f); - if (r==0) r = lt_c.write(fdata->f); - - // fill in time delta with silence if continuous output mode - if (fdata->continuous) { - time_t now = time(NULL); - if (now > st.st_mtime ) { - time_t delta = now - st.st_mtime; - if (delta > 3600) { - log(LOG_WARNING, "Too big time difference: %llu sec, limiting to one hour\n", - (unsigned long long)delta); - delta = 3600; - } - LameTone lt_silence(mixmode, 1000); - for (; (r==0 && delta > 1); --delta) - r = lt_silence.write(fdata->f); - } - } - - if (r==0) r = lt_c.write(fdata->f); - if (r==0) r = lt_b.write(fdata->f); - if (r==0) r = lt_a.write(fdata->f); - - if (r<0) fseek(fdata->f, st.st_size, SEEK_SET); - } - return 0; +static int open_file(file_data* fdata, mix_modes mixmode, int is_audio) { + int rename_result = rename_if_exists(fdata->file_path.c_str(), fdata->file_path_tmp.c_str()); + fdata->f = fopen(fdata->file_path_tmp.c_str(), fdata->append ? "a+" : "w"); + if (fdata->f == NULL) { + return -1; + } + + struct stat st = {}; + if (!fdata->append || fstat(fileno(fdata->f), &st) != 0 || st.st_size == 0) { + if (!fdata->split_on_transmission) { + log(LOG_INFO, "Writing to %s\n", fdata->file_path.c_str()); + } else { + debug_print("Writing to %s\n", fdata->file_path_tmp.c_str()); + } + return 0; + } + if (rename_result < 0) { + log(LOG_INFO, "Writing to %s\n", fdata->file_path.c_str()); + debug_print("Writing to %s\n", fdata->file_path_tmp.c_str()); + } else { + log(LOG_INFO, "Appending from pos %llu to %s\n", (unsigned long long)st.st_size, fdata->file_path.c_str()); + debug_print("Appending from pos %llu to %s\n", (unsigned long long)st.st_size, fdata->file_path_tmp.c_str()); + } + + if (is_audio) { + // fill missing space with marker tones + LameTone lt_a(mixmode, 120, 2222); + LameTone lt_b(mixmode, 120, 1111); + LameTone lt_c(mixmode, 120, 555); + + int r = lt_a.write(fdata->f); + if (r == 0) + r = lt_b.write(fdata->f); + if (r == 0) + r = lt_c.write(fdata->f); + + // fill in time delta with silence if continuous output mode + if (fdata->continuous) { + time_t now = time(NULL); + if (now > st.st_mtime) { + time_t delta = now - st.st_mtime; + if (delta > 3600) { + log(LOG_WARNING, "Too big time difference: %llu sec, limiting to one hour\n", (unsigned long long)delta); + delta = 3600; + } + LameTone lt_silence(mixmode, 1000); + for (; (r == 0 && delta > 1); --delta) + r = lt_silence.write(fdata->f); + } + } + + if (r == 0) + r = lt_c.write(fdata->f); + if (r == 0) + r = lt_b.write(fdata->f); + if (r == 0) + r = lt_a.write(fdata->f); + + if (r < 0) + fseek(fdata->f, st.st_size, SEEK_SET); + } + return 0; } -static void close_file(channel_t *channel, file_data *fdata) { - if (!fdata) { - return; - } - - if(fdata->type == O_FILE && fdata->f && channel->lame) { - int encoded = lame_encode_flush_nogap(channel->lame, channel->lamebuf, LAMEBUF_SIZE); - debug_print("closing file %s flushed %d\n", fdata->file_path.c_str(), encoded); - - if (encoded > 0) { - size_t written = fwrite((void *)channel->lamebuf, 1, (size_t)encoded, fdata->f); - if (written == 0 || written < (size_t)encoded) - log(LOG_WARNING, "Problem writing %s (%s)\n", fdata->file_path.c_str(), strerror(errno)); - } - } - - if (fdata->f) { - fclose(fdata->f); - fdata->f = NULL; - rename_if_exists(fdata->file_path_tmp.c_str(), fdata->file_path.c_str()); - } - fdata->file_path.clear(); - fdata->file_path_tmp.clear(); +static void close_file(channel_t* channel, file_data* fdata) { + if (!fdata) { + return; + } + + if (fdata->type == O_FILE && fdata->f && channel->lame) { + int encoded = lame_encode_flush_nogap(channel->lame, channel->lamebuf, LAMEBUF_SIZE); + debug_print("closing file %s flushed %d\n", fdata->file_path.c_str(), encoded); + + if (encoded > 0) { + size_t written = fwrite((void*)channel->lamebuf, 1, (size_t)encoded, fdata->f); + if (written == 0 || written < (size_t)encoded) + log(LOG_WARNING, "Problem writing %s (%s)\n", fdata->file_path.c_str(), strerror(errno)); + } + } + + if (fdata->f) { + fclose(fdata->f); + fdata->f = NULL; + rename_if_exists(fdata->file_path_tmp.c_str(), fdata->file_path.c_str()); + } + fdata->file_path.clear(); + fdata->file_path_tmp.clear(); } /* @@ -339,47 +344,45 @@ static void close_file(channel_t *channel, file_data *fdata) { * else (append or continuous) check: * if hour is different. */ -static void close_if_necessary(channel_t *channel, file_data *fdata) { - static const double MIN_TRANSMISSION_TIME_SEC = 1.0; - static const double MAX_TRANSMISSION_TIME_SEC = 60.0 * 60.0; - static const double MAX_TRANSMISSION_IDLE_SEC = 0.5; - - if (!fdata || !fdata->f) { - return; - } - - timeval current_time; - gettimeofday(¤t_time, NULL); - - if (fdata->split_on_transmission) { - double duration_sec = delta_sec(&fdata->open_time, ¤t_time); - double idle_sec = delta_sec(&fdata->last_write_time, ¤t_time); - - if (duration_sec > MAX_TRANSMISSION_TIME_SEC || - (duration_sec > MIN_TRANSMISSION_TIME_SEC && idle_sec > MAX_TRANSMISSION_IDLE_SEC)) { - debug_print("closing file %s, duration %f sec, idle %f sec\n", - fdata->file_path.c_str(), duration_sec, idle_sec); - close_file(channel, fdata); - } - return; - } - - // Check if the hour boundary was just crossed. NOTE: Actual hour number doesn't matter but still - // need to use localtime if enabled (some timezones have partial hour offsets) - int start_hour; - int current_hour; - if (use_localtime) { - start_hour = localtime(&(fdata->open_time.tv_sec))->tm_hour; - current_hour = localtime(¤t_time.tv_sec)->tm_hour; - } else { - start_hour = gmtime(&(fdata->open_time.tv_sec))->tm_hour; - current_hour = gmtime(¤t_time.tv_sec)->tm_hour; - } - - if (start_hour != current_hour) { - debug_print("closing file %s after crossing hour boundary\n", fdata->file_path.c_str()); - close_file(channel, fdata); - } +static void close_if_necessary(channel_t* channel, file_data* fdata) { + static const double MIN_TRANSMISSION_TIME_SEC = 1.0; + static const double MAX_TRANSMISSION_TIME_SEC = 60.0 * 60.0; + static const double MAX_TRANSMISSION_IDLE_SEC = 0.5; + + if (!fdata || !fdata->f) { + return; + } + + timeval current_time; + gettimeofday(¤t_time, NULL); + + if (fdata->split_on_transmission) { + double duration_sec = delta_sec(&fdata->open_time, ¤t_time); + double idle_sec = delta_sec(&fdata->last_write_time, ¤t_time); + + if (duration_sec > MAX_TRANSMISSION_TIME_SEC || (duration_sec > MIN_TRANSMISSION_TIME_SEC && idle_sec > MAX_TRANSMISSION_IDLE_SEC)) { + debug_print("closing file %s, duration %f sec, idle %f sec\n", fdata->file_path.c_str(), duration_sec, idle_sec); + close_file(channel, fdata); + } + return; + } + + // Check if the hour boundary was just crossed. NOTE: Actual hour number doesn't matter but still + // need to use localtime if enabled (some timezones have partial hour offsets) + int start_hour; + int current_hour; + if (use_localtime) { + start_hour = localtime(&(fdata->open_time.tv_sec))->tm_hour; + current_hour = localtime(¤t_time.tv_sec)->tm_hour; + } else { + start_hour = gmtime(&(fdata->open_time.tv_sec))->tm_hour; + current_hour = gmtime(¤t_time.tv_sec)->tm_hour; + } + + if (start_hour != current_hour) { + debug_print("closing file %s after crossing hour boundary\n", fdata->file_path.c_str()); + close_file(channel, fdata); + } } /* @@ -390,299 +393,291 @@ static void close_if_necessary(channel_t *channel, file_data *fdata) { * Otherwise, create a file name based on the current timestamp and * open that new file. If that file open succeeded, return true. */ -static bool output_file_ready(channel_t *channel, file_data *fdata, mix_modes mixmode, int is_audio) { - if (!fdata) { - return false; - } - - close_if_necessary(channel, fdata); - - if (fdata->f) { // still open - return true; - } - - timeval current_time; - gettimeofday(¤t_time, NULL); - struct tm *time; - if (use_localtime) { - time = localtime(¤t_time.tv_sec); - } else { - time = gmtime(¤t_time.tv_sec); - } - - char timestamp[32]; - if (strftime(timestamp, sizeof(timestamp), - fdata->split_on_transmission ? "_%Y%m%d_%H%M%S" : "_%Y%m%d_%H", - time) == 0) { - log(LOG_NOTICE, "strftime returned 0\n"); - return false; - } - - std::string output_dir; - if (fdata->dated_subdirectories) { - output_dir = make_dated_subdirs(fdata->basedir, time); - if (output_dir.empty()) { - log(LOG_ERR, "Failed to create dated subdirectory\n"); - return false; - } - } else { - output_dir = fdata->basedir; - make_dir(output_dir); - } - - // use a string stream to build the output filepath - std::stringstream ss; - ss << output_dir << '/' << fdata->basename << timestamp; - if (fdata->include_freq) { - ss << '_' << channel->freqlist[channel->freq_idx].frequency; - } - ss << fdata->suffix; - fdata->file_path = ss.str(); - - fdata->file_path_tmp = fdata->file_path + ".tmp"; - - fdata->open_time = fdata->last_write_time = current_time; - - if (open_file(fdata, mixmode, is_audio) < 0) { - log(LOG_WARNING, "Cannot open output file %s (%s)\n", fdata->file_path_tmp.c_str(), strerror(errno)); - return false; - } - - return true; +static bool output_file_ready(channel_t* channel, file_data* fdata, mix_modes mixmode, int is_audio) { + if (!fdata) { + return false; + } + + close_if_necessary(channel, fdata); + + if (fdata->f) { // still open + return true; + } + + timeval current_time; + gettimeofday(¤t_time, NULL); + struct tm* time; + if (use_localtime) { + time = localtime(¤t_time.tv_sec); + } else { + time = gmtime(¤t_time.tv_sec); + } + + char timestamp[32]; + if (strftime(timestamp, sizeof(timestamp), fdata->split_on_transmission ? "_%Y%m%d_%H%M%S" : "_%Y%m%d_%H", time) == 0) { + log(LOG_NOTICE, "strftime returned 0\n"); + return false; + } + + std::string output_dir; + if (fdata->dated_subdirectories) { + output_dir = make_dated_subdirs(fdata->basedir, time); + if (output_dir.empty()) { + log(LOG_ERR, "Failed to create dated subdirectory\n"); + return false; + } + } else { + output_dir = fdata->basedir; + make_dir(output_dir); + } + + // use a string stream to build the output filepath + std::stringstream ss; + ss << output_dir << '/' << fdata->basename << timestamp; + if (fdata->include_freq) { + ss << '_' << channel->freqlist[channel->freq_idx].frequency; + } + ss << fdata->suffix; + fdata->file_path = ss.str(); + + fdata->file_path_tmp = fdata->file_path + ".tmp"; + + fdata->open_time = fdata->last_write_time = current_time; + + if (open_file(fdata, mixmode, is_audio) < 0) { + log(LOG_WARNING, "Cannot open output file %s (%s)\n", fdata->file_path_tmp.c_str(), strerror(errno)); + return false; + } + + return true; } // Create all the output for a particular channel. -void process_outputs(channel_t *channel, int cur_scan_freq) { - int mp3_bytes = 0; - if(channel->need_mp3) { - //debug_bulk_print("channel->mode=%s\n", channel->mode == MM_STEREO ? "MM_STEREO" : "MM_MONO"); - mp3_bytes = lame_encode_buffer_ieee_float( - channel->lame, - channel->waveout, - (channel->mode == MM_STEREO ? channel->waveout_r : NULL), - WAVE_BATCH, - channel->lamebuf, - LAMEBUF_SIZE - ); - if (mp3_bytes < 0) - log(LOG_WARNING, "lame_encode_buffer_ieee_float: %d\n", mp3_bytes); - } - for (int k = 0; k < channel->output_count; k++) { - if(channel->outputs[k].enabled == false) continue; - if(channel->outputs[k].type == O_ICECAST) { - icecast_data *icecast = (icecast_data *)(channel->outputs[k].data); - if(icecast->shout == NULL || mp3_bytes <= 0) continue; - int ret = shout_send(icecast->shout, channel->lamebuf, mp3_bytes); - if (ret != SHOUTERR_SUCCESS || shout_queuelen(icecast->shout) > MAX_SHOUT_QUEUELEN) { - if (shout_queuelen(icecast->shout) > MAX_SHOUT_QUEUELEN) - log(LOG_WARNING, "Exceeded max backlog for %s:%d/%s, disconnecting\n", - icecast->hostname, icecast->port, icecast->mountpoint); - // reset connection - log(LOG_WARNING, "Lost connection to %s:%d/%s\n", - icecast->hostname, icecast->port, icecast->mountpoint); - shout_close(icecast->shout); - shout_free(icecast->shout); - icecast->shout = NULL; - } else if(icecast->send_scan_freq_tags && cur_scan_freq >= 0) { - shout_metadata_t *meta = shout_metadata_new(); - char description[32]; - if(channel->freqlist[channel->freq_idx].label != NULL) { - if (shout_metadata_add(meta, "song", channel->freqlist[channel->freq_idx].label) != SHOUTERR_SUCCESS) { - log(LOG_WARNING, "Failed to add shout metadata\n"); - } - } else { - snprintf(description, sizeof(description), "%.3f MHz", channel->freqlist[channel->freq_idx].frequency / 1000000.0); - if (shout_metadata_add(meta, "song", description) != SHOUTERR_SUCCESS) { - log(LOG_WARNING, "Failed to add shout metadata\n"); - } - } - if (SHOUT_SET_METADATA(icecast->shout, meta) != SHOUTERR_SUCCESS) { - log(LOG_WARNING, "Failed to add shout metadata\n"); - } - shout_metadata_free(meta); - } - } else if(channel->outputs[k].type == O_FILE || channel->outputs[k].type == O_RAWFILE) { - file_data *fdata = (file_data *)(channel->outputs[k].data); - - if (fdata->continuous == false && - channel->axcindicate == NO_SIGNAL && - channel->outputs[k].active == false) { - close_if_necessary(channel, fdata); - continue; - } - - if (channel->outputs[k].type == O_FILE && mp3_bytes <= 0) - continue; - - if (!output_file_ready(channel, fdata, channel->mode, (channel->outputs[k].type == O_RAWFILE ? 0 : 1))) { - log(LOG_WARNING, "Output disabled\n"); - channel->outputs[k].enabled = false; - continue; - }; - - size_t buflen = 0, written = 0; - if (channel->outputs[k].type == O_FILE) { - buflen = (size_t)mp3_bytes; - written = fwrite(channel->lamebuf, 1, buflen, fdata->f); - } else if(channel->outputs[k].type == O_RAWFILE) { - buflen = 2 * sizeof(float) * WAVE_BATCH; - written = fwrite(channel->iq_out, 1, buflen, fdata->f); - } - if(written < buflen) { - if(ferror(fdata->f)) - log(LOG_WARNING, "Cannot write to %s (%s), output disabled\n", - fdata->file_path.c_str(), strerror(errno)); - else - log(LOG_WARNING, "Short write on %s, output disabled\n", - fdata->file_path.c_str()); - close_file(channel, fdata); - channel->outputs[k].enabled = false; - } - channel->outputs[k].active = (channel->axcindicate != NO_SIGNAL); - gettimeofday(&fdata->last_write_time, NULL); - } else if(channel->outputs[k].type == O_MIXER) { - mixer_data *mdata = (mixer_data *)(channel->outputs[k].data); - mixer_put_samples(mdata->mixer, mdata->input, channel->waveout, channel->axcindicate != NO_SIGNAL, WAVE_BATCH); - } else if(channel->outputs[k].type == O_UDP_STREAM) { - udp_stream_data *sdata = (udp_stream_data *)channel->outputs[k].data; - - if(sdata->continuous == false && channel->axcindicate == NO_SIGNAL) { - continue; - } - - if(channel->mode == MM_MONO) { - udp_stream_write(sdata, channel->waveout, (size_t)WAVE_BATCH * sizeof(float)); - } else { - udp_stream_write(sdata, channel->waveout, channel->waveout_r, (size_t)WAVE_BATCH * sizeof(float)); - } +void process_outputs(channel_t* channel, int cur_scan_freq) { + int mp3_bytes = 0; + if (channel->need_mp3) { + // debug_bulk_print("channel->mode=%s\n", channel->mode == MM_STEREO ? "MM_STEREO" : "MM_MONO"); + mp3_bytes = lame_encode_buffer_ieee_float(channel->lame, channel->waveout, (channel->mode == MM_STEREO ? channel->waveout_r : NULL), WAVE_BATCH, channel->lamebuf, LAMEBUF_SIZE); + if (mp3_bytes < 0) + log(LOG_WARNING, "lame_encode_buffer_ieee_float: %d\n", mp3_bytes); + } + for (int k = 0; k < channel->output_count; k++) { + if (channel->outputs[k].enabled == false) + continue; + if (channel->outputs[k].type == O_ICECAST) { + icecast_data* icecast = (icecast_data*)(channel->outputs[k].data); + if (icecast->shout == NULL || mp3_bytes <= 0) + continue; + int ret = shout_send(icecast->shout, channel->lamebuf, mp3_bytes); + if (ret != SHOUTERR_SUCCESS || shout_queuelen(icecast->shout) > MAX_SHOUT_QUEUELEN) { + if (shout_queuelen(icecast->shout) > MAX_SHOUT_QUEUELEN) + log(LOG_WARNING, "Exceeded max backlog for %s:%d/%s, disconnecting\n", icecast->hostname, icecast->port, icecast->mountpoint); + // reset connection + log(LOG_WARNING, "Lost connection to %s:%d/%s\n", icecast->hostname, icecast->port, icecast->mountpoint); + shout_close(icecast->shout); + shout_free(icecast->shout); + icecast->shout = NULL; + } else if (icecast->send_scan_freq_tags && cur_scan_freq >= 0) { + shout_metadata_t* meta = shout_metadata_new(); + char description[32]; + if (channel->freqlist[channel->freq_idx].label != NULL) { + if (shout_metadata_add(meta, "song", channel->freqlist[channel->freq_idx].label) != SHOUTERR_SUCCESS) { + log(LOG_WARNING, "Failed to add shout metadata\n"); + } + } else { + snprintf(description, sizeof(description), "%.3f MHz", channel->freqlist[channel->freq_idx].frequency / 1000000.0); + if (shout_metadata_add(meta, "song", description) != SHOUTERR_SUCCESS) { + log(LOG_WARNING, "Failed to add shout metadata\n"); + } + } + if (SHOUT_SET_METADATA(icecast->shout, meta) != SHOUTERR_SUCCESS) { + log(LOG_WARNING, "Failed to add shout metadata\n"); + } + shout_metadata_free(meta); + } + } else if (channel->outputs[k].type == O_FILE || channel->outputs[k].type == O_RAWFILE) { + file_data* fdata = (file_data*)(channel->outputs[k].data); + + if (fdata->continuous == false && channel->axcindicate == NO_SIGNAL && channel->outputs[k].active == false) { + close_if_necessary(channel, fdata); + continue; + } + + if (channel->outputs[k].type == O_FILE && mp3_bytes <= 0) + continue; + + if (!output_file_ready(channel, fdata, channel->mode, (channel->outputs[k].type == O_RAWFILE ? 0 : 1))) { + log(LOG_WARNING, "Output disabled\n"); + channel->outputs[k].enabled = false; + continue; + }; + + size_t buflen = 0, written = 0; + if (channel->outputs[k].type == O_FILE) { + buflen = (size_t)mp3_bytes; + written = fwrite(channel->lamebuf, 1, buflen, fdata->f); + } else if (channel->outputs[k].type == O_RAWFILE) { + buflen = 2 * sizeof(float) * WAVE_BATCH; + written = fwrite(channel->iq_out, 1, buflen, fdata->f); + } + if (written < buflen) { + if (ferror(fdata->f)) + log(LOG_WARNING, "Cannot write to %s (%s), output disabled\n", fdata->file_path.c_str(), strerror(errno)); + else + log(LOG_WARNING, "Short write on %s, output disabled\n", fdata->file_path.c_str()); + close_file(channel, fdata); + channel->outputs[k].enabled = false; + } + channel->outputs[k].active = (channel->axcindicate != NO_SIGNAL); + gettimeofday(&fdata->last_write_time, NULL); + } else if (channel->outputs[k].type == O_MIXER) { + mixer_data* mdata = (mixer_data*)(channel->outputs[k].data); + mixer_put_samples(mdata->mixer, mdata->input, channel->waveout, channel->axcindicate != NO_SIGNAL, WAVE_BATCH); + } else if (channel->outputs[k].type == O_UDP_STREAM) { + udp_stream_data* sdata = (udp_stream_data*)channel->outputs[k].data; + + if (sdata->continuous == false && channel->axcindicate == NO_SIGNAL) { + continue; + } + + if (channel->mode == MM_MONO) { + udp_stream_write(sdata, channel->waveout, (size_t)WAVE_BATCH * sizeof(float)); + } else { + udp_stream_write(sdata, channel->waveout, channel->waveout_r, (size_t)WAVE_BATCH * sizeof(float)); + } #ifdef WITH_PULSEAUDIO - } else if(channel->outputs[k].type == O_PULSE) { - pulse_data *pdata = (pulse_data *)(channel->outputs[k].data); - if(pdata->continuous == false && channel->axcindicate == NO_SIGNAL) - continue; + } else if (channel->outputs[k].type == O_PULSE) { + pulse_data* pdata = (pulse_data*)(channel->outputs[k].data); + if (pdata->continuous == false && channel->axcindicate == NO_SIGNAL) + continue; - pulse_write_stream(pdata, channel->mode, channel->waveout, channel->waveout_r, (size_t)WAVE_BATCH * sizeof(float)); + pulse_write_stream(pdata, channel->mode, channel->waveout, channel->waveout_r, (size_t)WAVE_BATCH * sizeof(float)); #endif /* WITH_PULSEAUDIO */ - } - } + } + } } -void disable_channel_outputs(channel_t *channel) { - for (int k = 0; k < channel->output_count; k++) { - output_t *output = channel->outputs + k; - output->enabled = false; - if(output->type == O_ICECAST) { - icecast_data *icecast = (icecast_data *)(channel->outputs[k].data); - if(icecast->shout == NULL) continue; - log(LOG_WARNING, "Closing connection to %s:%d/%s\n", - icecast->hostname, icecast->port, icecast->mountpoint); - shout_close(icecast->shout); - shout_free(icecast->shout); - icecast->shout = NULL; - } else if(output->type == O_FILE || output->type == O_RAWFILE) { - file_data *fdata = (file_data *)(channel->outputs[k].data); - close_file(channel, fdata); - } else if(output->type == O_MIXER) { - mixer_data *mdata = (mixer_data *)(output->data); - mixer_disable_input(mdata->mixer, mdata->input); - } else if(output->type == O_UDP_STREAM) { - udp_stream_data *sdata = (udp_stream_data *)output->data; - udp_stream_shutdown(sdata); +void disable_channel_outputs(channel_t* channel) { + for (int k = 0; k < channel->output_count; k++) { + output_t* output = channel->outputs + k; + output->enabled = false; + if (output->type == O_ICECAST) { + icecast_data* icecast = (icecast_data*)(channel->outputs[k].data); + if (icecast->shout == NULL) + continue; + log(LOG_WARNING, "Closing connection to %s:%d/%s\n", icecast->hostname, icecast->port, icecast->mountpoint); + shout_close(icecast->shout); + shout_free(icecast->shout); + icecast->shout = NULL; + } else if (output->type == O_FILE || output->type == O_RAWFILE) { + file_data* fdata = (file_data*)(channel->outputs[k].data); + close_file(channel, fdata); + } else if (output->type == O_MIXER) { + mixer_data* mdata = (mixer_data*)(output->data); + mixer_disable_input(mdata->mixer, mdata->input); + } else if (output->type == O_UDP_STREAM) { + udp_stream_data* sdata = (udp_stream_data*)output->data; + udp_stream_shutdown(sdata); #ifdef WITH_PULSEAUDIO - } else if(output->type == O_PULSE) { - pulse_data *pdata = (pulse_data *)(output->data); - pulse_shutdown(pdata); + } else if (output->type == O_PULSE) { + pulse_data* pdata = (pulse_data*)(output->data); + pulse_shutdown(pdata); #endif /* WITH_PULSEAUDIO */ - } - } + } + } } -void disable_device_outputs(device_t *dev) { - log(LOG_INFO, "Disabling device outputs\n"); - for(int j = 0; j < dev->channel_count; j++) { - disable_channel_outputs(dev->channels + j); - } +void disable_device_outputs(device_t* dev) { + log(LOG_INFO, "Disabling device outputs\n"); + for (int j = 0; j < dev->channel_count; j++) { + disable_channel_outputs(dev->channels + j); + } } -static void print_channel_metric(FILE *f, char const *name, float freq, char *label) { - fprintf(f, "%s{freq=\"%.3f\"", name, freq / 1000000.0); - if (label != NULL) { - fprintf(f, ",label=\"%s\"", label); - } - fprintf(f, "}"); +static void print_channel_metric(FILE* f, char const* name, float freq, char* label) { + fprintf(f, "%s{freq=\"%.3f\"", name, freq / 1000000.0); + if (label != NULL) { + fprintf(f, ",label=\"%s\"", label); + } + fprintf(f, "}"); } -static void output_channel_noise_levels(FILE *f) { - fprintf(f, "# HELP channel_noise_level Raw squelch noise_level.\n" - "# TYPE channel_noise_level gauge\n"); - - for (int i = 0; i < device_count; i++) { - device_t* dev = devices + i; - for (int j = 0; j < dev->channel_count; j++) { - channel_t* channel = devices[i].channels + j; - for (int k = 0; k < channel->freq_count; k++) { - print_channel_metric(f, "channel_noise_level", channel->freqlist[k].frequency, channel->freqlist[k].label); - fprintf(f, "\t%.3f\n", channel->freqlist[k].squelch.noise_level()); - } - } - } - fprintf(f, "\n"); +static void output_channel_noise_levels(FILE* f) { + fprintf(f, + "# HELP channel_noise_level Raw squelch noise_level.\n" + "# TYPE channel_noise_level gauge\n"); + + for (int i = 0; i < device_count; i++) { + device_t* dev = devices + i; + for (int j = 0; j < dev->channel_count; j++) { + channel_t* channel = devices[i].channels + j; + for (int k = 0; k < channel->freq_count; k++) { + print_channel_metric(f, "channel_noise_level", channel->freqlist[k].frequency, channel->freqlist[k].label); + fprintf(f, "\t%.3f\n", channel->freqlist[k].squelch.noise_level()); + } + } + } + fprintf(f, "\n"); } -static void output_channel_dbfs_noise_levels(FILE *f) { - fprintf(f, "# HELP channel_dbfs_noise_level Squelch noise_level as dBFS.\n" - "# TYPE channel_dbfs_noise_level gauge\n"); - - for (int i = 0; i < device_count; i++) { - device_t* dev = devices + i; - for (int j = 0; j < dev->channel_count; j++) { - channel_t* channel = devices[i].channels + j; - for (int k = 0; k < channel->freq_count; k++) { - print_channel_metric(f, "channel_dbfs_noise_level", channel->freqlist[k].frequency, channel->freqlist[k].label); - fprintf(f, "\t%.3f\n", level_to_dBFS(channel->freqlist[k].squelch.noise_level())); - } - } - } - fprintf(f, "\n"); +static void output_channel_dbfs_noise_levels(FILE* f) { + fprintf(f, + "# HELP channel_dbfs_noise_level Squelch noise_level as dBFS.\n" + "# TYPE channel_dbfs_noise_level gauge\n"); + + for (int i = 0; i < device_count; i++) { + device_t* dev = devices + i; + for (int j = 0; j < dev->channel_count; j++) { + channel_t* channel = devices[i].channels + j; + for (int k = 0; k < channel->freq_count; k++) { + print_channel_metric(f, "channel_dbfs_noise_level", channel->freqlist[k].frequency, channel->freqlist[k].label); + fprintf(f, "\t%.3f\n", level_to_dBFS(channel->freqlist[k].squelch.noise_level())); + } + } + } + fprintf(f, "\n"); } -static void output_channel_signal_levels(FILE *f) { - fprintf(f, "# HELP channel_signal_level Raw squelch signal_level.\n" - "# TYPE channel_signal_level gauge\n"); - - for (int i = 0; i < device_count; i++) { - device_t* dev = devices + i; - for (int j = 0; j < dev->channel_count; j++) { - channel_t* channel = devices[i].channels + j; - for (int k = 0; k < channel->freq_count; k++) { - print_channel_metric(f, "channel_signal_level", channel->freqlist[k].frequency, channel->freqlist[k].label); - fprintf(f, "\t%.3f\n", channel->freqlist[k].squelch.signal_level()); - } - } - } - fprintf(f, "\n"); +static void output_channel_signal_levels(FILE* f) { + fprintf(f, + "# HELP channel_signal_level Raw squelch signal_level.\n" + "# TYPE channel_signal_level gauge\n"); + + for (int i = 0; i < device_count; i++) { + device_t* dev = devices + i; + for (int j = 0; j < dev->channel_count; j++) { + channel_t* channel = devices[i].channels + j; + for (int k = 0; k < channel->freq_count; k++) { + print_channel_metric(f, "channel_signal_level", channel->freqlist[k].frequency, channel->freqlist[k].label); + fprintf(f, "\t%.3f\n", channel->freqlist[k].squelch.signal_level()); + } + } + } + fprintf(f, "\n"); } -static void output_channel_dbfs_signal_levels(FILE *f) { - fprintf(f, "# HELP channel_dbfs_signal_level Squelch signal_level as dBFS.\n" - "# TYPE channel_dbfs_signal_level gauge\n"); - - for (int i = 0; i < device_count; i++) { - device_t* dev = devices + i; - for (int j = 0; j < dev->channel_count; j++) { - channel_t* channel = devices[i].channels + j; - for (int k = 0; k < channel->freq_count; k++) { - print_channel_metric(f, "channel_dbfs_signal_level", channel->freqlist[k].frequency, channel->freqlist[k].label); - fprintf(f, "\t%.3f\n", level_to_dBFS(channel->freqlist[k].squelch.signal_level())); - } - } - } - fprintf(f, "\n"); +static void output_channel_dbfs_signal_levels(FILE* f) { + fprintf(f, + "# HELP channel_dbfs_signal_level Squelch signal_level as dBFS.\n" + "# TYPE channel_dbfs_signal_level gauge\n"); + + for (int i = 0; i < device_count; i++) { + device_t* dev = devices + i; + for (int j = 0; j < dev->channel_count; j++) { + channel_t* channel = devices[i].channels + j; + for (int k = 0; k < channel->freq_count; k++) { + print_channel_metric(f, "channel_dbfs_signal_level", channel->freqlist[k].frequency, channel->freqlist[k].label); + fprintf(f, "\t%.3f\n", level_to_dBFS(channel->freqlist[k].squelch.signal_level())); + } + } + } + fprintf(f, "\n"); } -static void output_channel_squelch_levels(FILE *f) { - fprintf(f, "# HELP channel_squelch_level Squelch squelch_level.\n" +static void output_channel_squelch_levels(FILE* f) { + fprintf(f, + "# HELP channel_squelch_level Squelch squelch_level.\n" "# TYPE channel_squelch_level gauge\n"); for (int i = 0; i < device_count; i++) { @@ -698,307 +693,315 @@ static void output_channel_squelch_levels(FILE *f) { fprintf(f, "\n"); } -static void output_channel_squelch_counter(FILE *f) { - fprintf(f, "# HELP channel_squelch_counter Squelch open_count.\n" - "# TYPE channel_squelch_counter counter\n"); - - for (int i = 0; i < device_count; i++) { - device_t* dev = devices + i; - for (int j = 0; j < dev->channel_count; j++) { - channel_t* channel = devices[i].channels + j; - for (int k = 0; k < channel->freq_count; k++) { - print_channel_metric(f, "channel_squelch_counter", channel->freqlist[k].frequency, channel->freqlist[k].label); - fprintf(f, "\t%zu\n", channel->freqlist[k].squelch.open_count()); - } - } - } - fprintf(f, "\n"); +static void output_channel_squelch_counter(FILE* f) { + fprintf(f, + "# HELP channel_squelch_counter Squelch open_count.\n" + "# TYPE channel_squelch_counter counter\n"); + + for (int i = 0; i < device_count; i++) { + device_t* dev = devices + i; + for (int j = 0; j < dev->channel_count; j++) { + channel_t* channel = devices[i].channels + j; + for (int k = 0; k < channel->freq_count; k++) { + print_channel_metric(f, "channel_squelch_counter", channel->freqlist[k].frequency, channel->freqlist[k].label); + fprintf(f, "\t%zu\n", channel->freqlist[k].squelch.open_count()); + } + } + } + fprintf(f, "\n"); } -static void output_channel_flappy_counter(FILE *f) { - fprintf(f, "# HELP channel_flappy_counter Squelch flappy_count.\n" - "# TYPE channel_flappy_counter counter\n"); - - for (int i = 0; i < device_count; i++) { - device_t* dev = devices + i; - for (int j = 0; j < dev->channel_count; j++) { - channel_t* channel = devices[i].channels + j; - for (int k = 0; k < channel->freq_count; k++) { - print_channel_metric(f, "channel_flappy_counter", channel->freqlist[k].frequency, channel->freqlist[k].label); - fprintf(f, "\t%zu\n", channel->freqlist[k].squelch.flappy_count()); - } - } - } - fprintf(f, "\n"); +static void output_channel_flappy_counter(FILE* f) { + fprintf(f, + "# HELP channel_flappy_counter Squelch flappy_count.\n" + "# TYPE channel_flappy_counter counter\n"); + + for (int i = 0; i < device_count; i++) { + device_t* dev = devices + i; + for (int j = 0; j < dev->channel_count; j++) { + channel_t* channel = devices[i].channels + j; + for (int k = 0; k < channel->freq_count; k++) { + print_channel_metric(f, "channel_flappy_counter", channel->freqlist[k].frequency, channel->freqlist[k].label); + fprintf(f, "\t%zu\n", channel->freqlist[k].squelch.flappy_count()); + } + } + } + fprintf(f, "\n"); } -static void output_channel_ctcss_counter(FILE *f) { - fprintf(f, "# HELP channel_ctcss_counter count of windows with CTCSS detected.\n" - "# TYPE channel_ctcss_counter counter\n"); - - for (int i = 0; i < device_count; i++) { - device_t* dev = devices + i; - for (int j = 0; j < dev->channel_count; j++) { - channel_t* channel = devices[i].channels + j; - for (int k = 0; k < channel->freq_count; k++) { - print_channel_metric(f, "channel_ctcss_counter", channel->freqlist[k].frequency, channel->freqlist[k].label); - fprintf(f, "\t%zu\n", channel->freqlist[k].squelch.ctcss_count()); - } - } - } - fprintf(f, "\n"); +static void output_channel_ctcss_counter(FILE* f) { + fprintf(f, + "# HELP channel_ctcss_counter count of windows with CTCSS detected.\n" + "# TYPE channel_ctcss_counter counter\n"); + + for (int i = 0; i < device_count; i++) { + device_t* dev = devices + i; + for (int j = 0; j < dev->channel_count; j++) { + channel_t* channel = devices[i].channels + j; + for (int k = 0; k < channel->freq_count; k++) { + print_channel_metric(f, "channel_ctcss_counter", channel->freqlist[k].frequency, channel->freqlist[k].label); + fprintf(f, "\t%zu\n", channel->freqlist[k].squelch.ctcss_count()); + } + } + } + fprintf(f, "\n"); } -static void output_channel_no_ctcss_counter(FILE *f) { - fprintf(f, "# HELP channel_no_ctcss_counter count of windows without CTCSS detected.\n" - "# TYPE channel_no_ctcss_counter counter\n"); - - for (int i = 0; i < device_count; i++) { - device_t* dev = devices + i; - for (int j = 0; j < dev->channel_count; j++) { - channel_t* channel = devices[i].channels + j; - for (int k = 0; k < channel->freq_count; k++) { - print_channel_metric(f, "channel_no_ctcss_counter", channel->freqlist[k].frequency, channel->freqlist[k].label); - fprintf(f, "\t%zu\n", channel->freqlist[k].squelch.no_ctcss_count()); - } - } - } - fprintf(f, "\n"); +static void output_channel_no_ctcss_counter(FILE* f) { + fprintf(f, + "# HELP channel_no_ctcss_counter count of windows without CTCSS detected.\n" + "# TYPE channel_no_ctcss_counter counter\n"); + + for (int i = 0; i < device_count; i++) { + device_t* dev = devices + i; + for (int j = 0; j < dev->channel_count; j++) { + channel_t* channel = devices[i].channels + j; + for (int k = 0; k < channel->freq_count; k++) { + print_channel_metric(f, "channel_no_ctcss_counter", channel->freqlist[k].frequency, channel->freqlist[k].label); + fprintf(f, "\t%zu\n", channel->freqlist[k].squelch.no_ctcss_count()); + } + } + } + fprintf(f, "\n"); } -static void output_channel_activity_counters(FILE *f) { - fprintf(f, "# HELP channel_activity_counter Loops of output_thread with frequency active.\n" - "# TYPE channel_activity_counter counter\n"); - - for (int i = 0; i < device_count; i++) { - device_t* dev = devices + i; - for (int j = 0; j < dev->channel_count; j++) { - channel_t* channel = devices[i].channels + j; - for (int k = 0; k < channel->freq_count; k++) { - print_channel_metric(f, "channel_activity_counter", channel->freqlist[k].frequency, channel->freqlist[k].label); - fprintf(f, "\t%zu\n", channel->freqlist[k].active_counter); - } - } - } - fprintf(f, "\n"); +static void output_channel_activity_counters(FILE* f) { + fprintf(f, + "# HELP channel_activity_counter Loops of output_thread with frequency active.\n" + "# TYPE channel_activity_counter counter\n"); + + for (int i = 0; i < device_count; i++) { + device_t* dev = devices + i; + for (int j = 0; j < dev->channel_count; j++) { + channel_t* channel = devices[i].channels + j; + for (int k = 0; k < channel->freq_count; k++) { + print_channel_metric(f, "channel_activity_counter", channel->freqlist[k].frequency, channel->freqlist[k].label); + fprintf(f, "\t%zu\n", channel->freqlist[k].active_counter); + } + } + } + fprintf(f, "\n"); } -static void output_device_buffer_overflows(FILE *f) { - fprintf(f, "# HELP buffer_overflow_count Number of times a device's buffer has overflowed.\n" - "# TYPE buffer_overflow_count counter\n"); +static void output_device_buffer_overflows(FILE* f) { + fprintf(f, + "# HELP buffer_overflow_count Number of times a device's buffer has overflowed.\n" + "# TYPE buffer_overflow_count counter\n"); - for (int i = 0; i < device_count; i++) { - device_t* dev = devices + i; - fprintf(f, "buffer_overflow_count{device=\"%d\"}\t%zu\n", i, dev->input->overflow_count); - } - fprintf(f, "\n"); + for (int i = 0; i < device_count; i++) { + device_t* dev = devices + i; + fprintf(f, "buffer_overflow_count{device=\"%d\"}\t%zu\n", i, dev->input->overflow_count); + } + fprintf(f, "\n"); } -static void output_output_overruns(FILE *f) { - fprintf(f, "# HELP output_overrun_count Number of times a device or mixer output has overrun.\n" - "# TYPE output_overrun_count counter\n"); - - for (int i = 0; i < device_count; i++) { - device_t* dev = devices + i; - fprintf(f, "output_overrun_count{device=\"%d\"}\t%zu\n", i, dev->output_overrun_count); - } - for (int i = 0; i < mixer_count; i++) { - mixer_t* mixer = mixers + i; - fprintf(f, "output_overrun_count{mixer=\"%d\"}\t%zu\n", i, mixer->output_overrun_count); - } - fprintf(f, "\n"); +static void output_output_overruns(FILE* f) { + fprintf(f, + "# HELP output_overrun_count Number of times a device or mixer output has overrun.\n" + "# TYPE output_overrun_count counter\n"); + + for (int i = 0; i < device_count; i++) { + device_t* dev = devices + i; + fprintf(f, "output_overrun_count{device=\"%d\"}\t%zu\n", i, dev->output_overrun_count); + } + for (int i = 0; i < mixer_count; i++) { + mixer_t* mixer = mixers + i; + fprintf(f, "output_overrun_count{mixer=\"%d\"}\t%zu\n", i, mixer->output_overrun_count); + } + fprintf(f, "\n"); } -static void output_input_overruns(FILE *f) { - if (mixer_count == 0) { - return; - } - - fprintf(f, "# HELP input_overrun_count Number of times mixer input has overrun.\n" - "# TYPE input_overrun_count counter\n"); - - for (int i = 0; i < mixer_count; i++) { - mixer_t* mixer = mixers + i; - for (int j = 0; j < mixer->input_count; j++) { - mixinput_t *input = mixer->inputs + j; - fprintf(f, "input_overrun_count{mixer=\"%d\",input=\"%d\"}\t%zu\n", i, j, input->input_overrun_count); - } - } - fprintf(f, "\n"); +static void output_input_overruns(FILE* f) { + if (mixer_count == 0) { + return; + } + + fprintf(f, + "# HELP input_overrun_count Number of times mixer input has overrun.\n" + "# TYPE input_overrun_count counter\n"); + + for (int i = 0; i < mixer_count; i++) { + mixer_t* mixer = mixers + i; + for (int j = 0; j < mixer->input_count; j++) { + mixinput_t* input = mixer->inputs + j; + fprintf(f, "input_overrun_count{mixer=\"%d\",input=\"%d\"}\t%zu\n", i, j, input->input_overrun_count); + } + } + fprintf(f, "\n"); } -void write_stats_file(timeval *last_stats_write) { - if (!stats_filepath) { - return; - } - - timeval current_time; - gettimeofday(¤t_time, NULL); - - static const double STATS_FILE_TIMING = 15.0; - if (!do_exit && delta_sec(last_stats_write, ¤t_time) < STATS_FILE_TIMING) { - return; - } - - *last_stats_write = current_time; - - FILE *file = fopen(stats_filepath, "w"); - if (!file) { - log(LOG_WARNING, "Cannot open output file %s (%s)\n", stats_filepath, strerror(errno)); - return; - } - - output_channel_activity_counters(file); - output_channel_noise_levels(file); - output_channel_dbfs_noise_levels(file); - output_channel_signal_levels(file); - output_channel_dbfs_signal_levels(file); - output_channel_squelch_counter(file); - output_channel_squelch_levels(file); - output_channel_flappy_counter(file); - output_channel_ctcss_counter(file); - output_channel_no_ctcss_counter(file); - output_device_buffer_overflows(file); - output_output_overruns(file); - output_input_overruns(file); - - fclose(file); +void write_stats_file(timeval* last_stats_write) { + if (!stats_filepath) { + return; + } + + timeval current_time; + gettimeofday(¤t_time, NULL); + + static const double STATS_FILE_TIMING = 15.0; + if (!do_exit && delta_sec(last_stats_write, ¤t_time) < STATS_FILE_TIMING) { + return; + } + + *last_stats_write = current_time; + + FILE* file = fopen(stats_filepath, "w"); + if (!file) { + log(LOG_WARNING, "Cannot open output file %s (%s)\n", stats_filepath, strerror(errno)); + return; + } + + output_channel_activity_counters(file); + output_channel_noise_levels(file); + output_channel_dbfs_noise_levels(file); + output_channel_signal_levels(file); + output_channel_dbfs_signal_levels(file); + output_channel_squelch_counter(file); + output_channel_squelch_levels(file); + output_channel_flappy_counter(file); + output_channel_ctcss_counter(file); + output_channel_no_ctcss_counter(file); + output_device_buffer_overflows(file); + output_output_overruns(file); + output_input_overruns(file); + + fclose(file); } -void* output_thread(void *param) { - assert(param != NULL); - output_params_t *output_param = (output_params_t *)param; - struct freq_tag tag; - struct timeval tv; - int new_freq = -1; - timeval last_stats_write = {0, 0}; +void* output_thread(void* param) { + assert(param != NULL); + output_params_t* output_param = (output_params_t*)param; + struct freq_tag tag; + struct timeval tv; + int new_freq = -1; + timeval last_stats_write = {0, 0}; - debug_print("Starting output thread, devices %d:%d, mixers %d:%d, signal %p\n", output_param->device_start, output_param->device_end, output_param->mixer_start, output_param->mixer_end, output_param->mp3_signal); + debug_print("Starting output thread, devices %d:%d, mixers %d:%d, signal %p\n", output_param->device_start, output_param->device_end, output_param->mixer_start, output_param->mixer_end, + output_param->mp3_signal); #ifdef DEBUG - timeval ts, te; - gettimeofday(&ts, NULL); + timeval ts, te; + gettimeofday(&ts, NULL); #endif /* DEBUG */ - while (!do_exit) { - output_param->mp3_signal->wait(); - for (int i = output_param->mixer_start; i < output_param->mixer_end; i++) { - if(mixers[i].enabled == false) continue; - channel_t *channel = &mixers[i].channel; - if(channel->state == CH_READY) { - process_outputs(channel, -1); - channel->state = CH_DIRTY; - } - } + while (!do_exit) { + output_param->mp3_signal->wait(); + for (int i = output_param->mixer_start; i < output_param->mixer_end; i++) { + if (mixers[i].enabled == false) + continue; + channel_t* channel = &mixers[i].channel; + if (channel->state == CH_READY) { + process_outputs(channel, -1); + channel->state = CH_DIRTY; + } + } #ifdef DEBUG - gettimeofday(&te, NULL); - debug_bulk_print("mixeroutput: %lu.%lu %lu\n", te.tv_sec, (unsigned long) te.tv_usec, (te.tv_sec - ts.tv_sec) * 1000000UL + te.tv_usec - ts.tv_usec); - ts.tv_sec = te.tv_sec; - ts.tv_usec = te.tv_usec; + gettimeofday(&te, NULL); + debug_bulk_print("mixeroutput: %lu.%lu %lu\n", te.tv_sec, (unsigned long)te.tv_usec, (te.tv_sec - ts.tv_sec) * 1000000UL + te.tv_usec - ts.tv_usec); + ts.tv_sec = te.tv_sec; + ts.tv_usec = te.tv_usec; #endif /* DEBUG */ - for (int i = output_param->device_start; i < output_param->device_end; i++) { - device_t* dev = devices + i; - if (dev->input->state == INPUT_RUNNING && dev->waveavail) { - if(dev->mode == R_SCAN) { - tag_queue_get(dev, &tag); - if(tag.freq >= 0) { - tag.tv.tv_sec += shout_metadata_delay; - gettimeofday(&tv, NULL); - if(tag.tv.tv_sec < tv.tv_sec || (tag.tv.tv_sec == tv.tv_sec && tag.tv.tv_usec <= tv.tv_usec)) { - new_freq = tag.freq; - tag_queue_advance(dev); - } - } - } - for (int j = 0; j < dev->channel_count; j++) { - channel_t* channel = devices[i].channels + j; - process_outputs(channel, new_freq); - memcpy(channel->waveout, channel->waveout + WAVE_BATCH, AGC_EXTRA * 4); - } - dev->waveavail = 0; - } -// make sure we don't carry new_freq value to the next receiver which might be working -// in multichannel mode - new_freq = -1; - } - if(output_param->device_start == 0) { - write_stats_file(&last_stats_write); - } - } - return 0; + for (int i = output_param->device_start; i < output_param->device_end; i++) { + device_t* dev = devices + i; + if (dev->input->state == INPUT_RUNNING && dev->waveavail) { + if (dev->mode == R_SCAN) { + tag_queue_get(dev, &tag); + if (tag.freq >= 0) { + tag.tv.tv_sec += shout_metadata_delay; + gettimeofday(&tv, NULL); + if (tag.tv.tv_sec < tv.tv_sec || (tag.tv.tv_sec == tv.tv_sec && tag.tv.tv_usec <= tv.tv_usec)) { + new_freq = tag.freq; + tag_queue_advance(dev); + } + } + } + for (int j = 0; j < dev->channel_count; j++) { + channel_t* channel = devices[i].channels + j; + process_outputs(channel, new_freq); + memcpy(channel->waveout, channel->waveout + WAVE_BATCH, AGC_EXTRA * 4); + } + dev->waveavail = 0; + } + // make sure we don't carry new_freq value to the next receiver which might be working + // in multichannel mode + new_freq = -1; + } + if (output_param->device_start == 0) { + write_stats_file(&last_stats_write); + } + } + return 0; } // reconnect as required void* output_check_thread(void*) { - while (!do_exit) { - SLEEP(10000); - for (int i = 0; i < device_count; i++) { - device_t* dev = devices + i; - for (int j = 0; j < dev->channel_count; j++) { - for (int k = 0; k < dev->channels[j].output_count; k++) { - if(dev->channels[j].outputs[k].type == O_ICECAST) { - icecast_data *icecast = (icecast_data *)(dev->channels[j].outputs[k].data); - if(dev->input->state == INPUT_FAILED) { - if(icecast->shout) { - log(LOG_WARNING, "Device #%d failed, disconnecting stream %s:%d/%s\n", - i, icecast->hostname, icecast->port, icecast->mountpoint); - shout_close(icecast->shout); - shout_free(icecast->shout); - icecast->shout = NULL; - } - } else if(dev->input->state == INPUT_RUNNING) { - if (icecast->shout == NULL){ - log(LOG_NOTICE, "Trying to reconnect to %s:%d/%s...\n", - icecast->hostname, icecast->port, icecast->mountpoint); - shout_setup(icecast, dev->channels[j].mode); - } - } - } else if(dev->channels[j].outputs[k].type == O_UDP_STREAM) { - udp_stream_data *sdata = (udp_stream_data *)dev->channels[j].outputs[k].data; - - if(dev->input->state == INPUT_FAILED) { - udp_stream_shutdown(sdata); - } + while (!do_exit) { + SLEEP(10000); + for (int i = 0; i < device_count; i++) { + device_t* dev = devices + i; + for (int j = 0; j < dev->channel_count; j++) { + for (int k = 0; k < dev->channels[j].output_count; k++) { + if (dev->channels[j].outputs[k].type == O_ICECAST) { + icecast_data* icecast = (icecast_data*)(dev->channels[j].outputs[k].data); + if (dev->input->state == INPUT_FAILED) { + if (icecast->shout) { + log(LOG_WARNING, "Device #%d failed, disconnecting stream %s:%d/%s\n", i, icecast->hostname, icecast->port, icecast->mountpoint); + shout_close(icecast->shout); + shout_free(icecast->shout); + icecast->shout = NULL; + } + } else if (dev->input->state == INPUT_RUNNING) { + if (icecast->shout == NULL) { + log(LOG_NOTICE, "Trying to reconnect to %s:%d/%s...\n", icecast->hostname, icecast->port, icecast->mountpoint); + shout_setup(icecast, dev->channels[j].mode); + } + } + } else if (dev->channels[j].outputs[k].type == O_UDP_STREAM) { + udp_stream_data* sdata = (udp_stream_data*)dev->channels[j].outputs[k].data; + + if (dev->input->state == INPUT_FAILED) { + udp_stream_shutdown(sdata); + } #ifdef WITH_PULSEAUDIO - } else if(dev->channels[j].outputs[k].type == O_PULSE) { - pulse_data *pdata = (pulse_data *)(dev->channels[j].outputs[k].data); - if(dev->input->state == INPUT_FAILED) { - if(pdata->context) { - pulse_shutdown(pdata); - } - } else if(dev->input->state == INPUT_RUNNING) { - if (pdata->context == NULL){ - pulse_setup(pdata, dev->channels[j].mode); - } - } + } else if (dev->channels[j].outputs[k].type == O_PULSE) { + pulse_data* pdata = (pulse_data*)(dev->channels[j].outputs[k].data); + if (dev->input->state == INPUT_FAILED) { + if (pdata->context) { + pulse_shutdown(pdata); + } + } else if (dev->input->state == INPUT_RUNNING) { + if (pdata->context == NULL) { + pulse_setup(pdata, dev->channels[j].mode); + } + } #endif /* WITH_PULSEAUDIO */ - } - } - } - } - for (int i = 0; i < mixer_count; i++) { - if(mixers[i].enabled == false) continue; - for (int k = 0; k < mixers[i].channel.output_count; k++) { - if(mixers[i].channel.outputs[k].enabled == false) - continue; - if(mixers[i].channel.outputs[k].type == O_ICECAST) { - icecast_data *icecast = (icecast_data *)(mixers[i].channel.outputs[k].data); - if(icecast->shout == NULL) { - log(LOG_NOTICE, "Trying to reconnect to %s:%d/%s...\n", - icecast->hostname, icecast->port, icecast->mountpoint); - shout_setup(icecast, mixers[i].channel.mode); - } + } + } + } + } + for (int i = 0; i < mixer_count; i++) { + if (mixers[i].enabled == false) + continue; + for (int k = 0; k < mixers[i].channel.output_count; k++) { + if (mixers[i].channel.outputs[k].enabled == false) + continue; + if (mixers[i].channel.outputs[k].type == O_ICECAST) { + icecast_data* icecast = (icecast_data*)(mixers[i].channel.outputs[k].data); + if (icecast->shout == NULL) { + log(LOG_NOTICE, "Trying to reconnect to %s:%d/%s...\n", icecast->hostname, icecast->port, icecast->mountpoint); + shout_setup(icecast, mixers[i].channel.mode); + } #ifdef WITH_PULSEAUDIO - } else if(mixers[i].channel.outputs[k].type == O_PULSE) { - pulse_data *pdata = (pulse_data *)(mixers[i].channel.outputs[k].data); - if (pdata->context == NULL){ - pulse_setup(pdata, mixers[i].channel.mode); - } + } else if (mixers[i].channel.outputs[k].type == O_PULSE) { + pulse_data* pdata = (pulse_data*)(mixers[i].channel.outputs[k].data); + if (pdata->context == NULL) { + pulse_setup(pdata, mixers[i].channel.mode); + } #endif /* WITH_PULSEAUDIO */ - } - } - } - } - return 0; + } + } + } + } + return 0; } // vim: ts=4 diff --git a/src/pulse.cpp b/src/pulse.cpp index 69d7226..ce2a3fa 100644 --- a/src/pulse.cpp +++ b/src/pulse.cpp @@ -18,237 +18,232 @@ * along with this program. If not, see . */ -#include -#include #include +#include +#include #include "rtl_airband.h" #define SERVER_IFNOTNULL(x) ((x) ? (x) : "") -#define PA_LOOP_LOCK(x) if(!pa_threaded_mainloop_in_thread(x)) { pa_threaded_mainloop_lock(x); } -#define PA_LOOP_UNLOCK(x) if(!pa_threaded_mainloop_in_thread(x)) { pa_threaded_mainloop_unlock(x); } +#define PA_LOOP_LOCK(x) \ + if (!pa_threaded_mainloop_in_thread(x)) { \ + pa_threaded_mainloop_lock(x); \ + } +#define PA_LOOP_UNLOCK(x) \ + if (!pa_threaded_mainloop_in_thread(x)) { \ + pa_threaded_mainloop_unlock(x); \ + } using namespace std; -pa_threaded_mainloop *mainloop = NULL; - -void pulse_shutdown(pulse_data *pdata) { - if(!pdata) - return; - PA_LOOP_LOCK(mainloop); - if(pdata->left) { - pa_stream_disconnect(pdata->left); - pa_stream_unref(pdata->left); - pdata->left = NULL; - } - if(pdata->right) { - pa_stream_disconnect(pdata->right); - pa_stream_unref(pdata->right); - pdata->right = NULL; - } - if(pdata->context) { - pa_context_disconnect(pdata->context); - pa_context_unref(pdata->context); - pdata->context = NULL; - } - PA_LOOP_UNLOCK(mainloop); +pa_threaded_mainloop* mainloop = NULL; + +void pulse_shutdown(pulse_data* pdata) { + if (!pdata) + return; + PA_LOOP_LOCK(mainloop); + if (pdata->left) { + pa_stream_disconnect(pdata->left); + pa_stream_unref(pdata->left); + pdata->left = NULL; + } + if (pdata->right) { + pa_stream_disconnect(pdata->right); + pa_stream_unref(pdata->right); + pdata->right = NULL; + } + if (pdata->context) { + pa_context_disconnect(pdata->context); + pa_context_unref(pdata->context); + pdata->context = NULL; + } + PA_LOOP_UNLOCK(mainloop); } -static void pulse_stream_underflow_cb(pa_stream *, void *userdata) { - pulse_data *pdata = (pulse_data *)userdata; - if(pdata->continuous) // do not flood the logs on every squelch closing - log(LOG_INFO, "pulse: %s: stream \"%s\": underflow\n", SERVER_IFNOTNULL(pdata->server), pdata->stream_name); +static void pulse_stream_underflow_cb(pa_stream*, void* userdata) { + pulse_data* pdata = (pulse_data*)userdata; + if (pdata->continuous) // do not flood the logs on every squelch closing + log(LOG_INFO, "pulse: %s: stream \"%s\": underflow\n", SERVER_IFNOTNULL(pdata->server), pdata->stream_name); } -static void pulse_stream_overflow_cb(pa_stream *, void *userdata) { - pulse_data *pdata = (pulse_data *)userdata; - log(LOG_INFO, "pulse: %s: stream \"%s\": overflow\n", SERVER_IFNOTNULL(pdata->server), pdata->stream_name); +static void pulse_stream_overflow_cb(pa_stream*, void* userdata) { + pulse_data* pdata = (pulse_data*)userdata; + log(LOG_INFO, "pulse: %s: stream \"%s\": overflow\n", SERVER_IFNOTNULL(pdata->server), pdata->stream_name); } -static void stream_state_cb(pa_stream *stream, void *userdata) { - pulse_data *pdata = (pulse_data *)userdata; - - switch (pa_stream_get_state(stream)) { - case PA_STREAM_READY: - if(pdata->mode == MM_MONO || - (pa_stream_get_state(pdata->left) == PA_STREAM_READY && - pa_stream_get_state(pdata->right) == PA_STREAM_READY)) - pa_stream_cork(pdata->left, 0, NULL, NULL); - break; - case PA_STREAM_UNCONNECTED: - case PA_STREAM_CREATING: - break; - case PA_STREAM_FAILED: - log(LOG_WARNING, "pulse: %s: stream \"%s\" failed: %s\n", SERVER_IFNOTNULL(pdata->server), pdata->stream_name, - pa_strerror(pa_context_errno(pdata->context))); - break; - case PA_STREAM_TERMINATED: - log(LOG_WARNING, "pulse: %s: stream \"%s\" terminated\n", SERVER_IFNOTNULL(pdata->server), pdata->stream_name); - break; - break; - } +static void stream_state_cb(pa_stream* stream, void* userdata) { + pulse_data* pdata = (pulse_data*)userdata; + + switch (pa_stream_get_state(stream)) { + case PA_STREAM_READY: + if (pdata->mode == MM_MONO || (pa_stream_get_state(pdata->left) == PA_STREAM_READY && pa_stream_get_state(pdata->right) == PA_STREAM_READY)) + pa_stream_cork(pdata->left, 0, NULL, NULL); + break; + case PA_STREAM_UNCONNECTED: + case PA_STREAM_CREATING: + break; + case PA_STREAM_FAILED: + log(LOG_WARNING, "pulse: %s: stream \"%s\" failed: %s\n", SERVER_IFNOTNULL(pdata->server), pdata->stream_name, pa_strerror(pa_context_errno(pdata->context))); + break; + case PA_STREAM_TERMINATED: + log(LOG_WARNING, "pulse: %s: stream \"%s\" terminated\n", SERVER_IFNOTNULL(pdata->server), pdata->stream_name); + break; + break; + } } -static pa_stream *pulse_setup_stream(pulse_data *pdata, const pa_sample_spec *ss, pa_channel_map *cmap, pa_stream *sync_stream) { - pa_stream *stream = NULL; - PA_LOOP_LOCK(mainloop); - if(!(stream = pa_stream_new(pdata->context, pdata->stream_name, ss, cmap))) { - log(LOG_ERR, "pulse: %s: failed to create stream \"%s\": %s\n", - SERVER_IFNOTNULL(pdata->server), pdata->stream_name, pa_strerror(pa_context_errno(pdata->context))); - goto fail; - } - pa_stream_set_state_callback(stream, stream_state_cb, pdata); - pa_stream_set_underflow_callback(stream, pulse_stream_underflow_cb, pdata); - pa_stream_set_overflow_callback(stream, pulse_stream_overflow_cb, pdata); -// Initially streams are corked (paused). For mono streams this is irrelevant, -// but for stereo mixers it's required to keep left and right channels in sync. -// Starting the left channel stream before the other stream from the sync pair is -// set up causes the left channel stream to fail. - if(pa_stream_connect_playback(stream, pdata->sink, NULL, - (pa_stream_flags_t)(PA_STREAM_INTERPOLATE_TIMING - |PA_STREAM_ADJUST_LATENCY - |PA_STREAM_START_CORKED - |PA_STREAM_AUTO_TIMING_UPDATE), NULL, sync_stream) < 0) { - log(LOG_ERR, "pulse: %s: failed to connect stream \"%s\": %s\n", - SERVER_IFNOTNULL(pdata->server), pdata->stream_name, pa_strerror(pa_context_errno(pdata->context))); - goto fail; - } - log(LOG_INFO, "pulse: %s: stream \"%s\" connected\n", SERVER_IFNOTNULL(pdata->server), pdata->stream_name); - PA_LOOP_UNLOCK(mainloop); - return stream; +static pa_stream* pulse_setup_stream(pulse_data* pdata, const pa_sample_spec* ss, pa_channel_map* cmap, pa_stream* sync_stream) { + pa_stream* stream = NULL; + PA_LOOP_LOCK(mainloop); + if (!(stream = pa_stream_new(pdata->context, pdata->stream_name, ss, cmap))) { + log(LOG_ERR, "pulse: %s: failed to create stream \"%s\": %s\n", SERVER_IFNOTNULL(pdata->server), pdata->stream_name, pa_strerror(pa_context_errno(pdata->context))); + goto fail; + } + pa_stream_set_state_callback(stream, stream_state_cb, pdata); + pa_stream_set_underflow_callback(stream, pulse_stream_underflow_cb, pdata); + pa_stream_set_overflow_callback(stream, pulse_stream_overflow_cb, pdata); + // Initially streams are corked (paused). For mono streams this is irrelevant, + // but for stereo mixers it's required to keep left and right channels in sync. + // Starting the left channel stream before the other stream from the sync pair is + // set up causes the left channel stream to fail. + if (pa_stream_connect_playback(stream, pdata->sink, NULL, (pa_stream_flags_t)(PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_ADJUST_LATENCY | PA_STREAM_START_CORKED | PA_STREAM_AUTO_TIMING_UPDATE), + NULL, sync_stream) < 0) { + log(LOG_ERR, "pulse: %s: failed to connect stream \"%s\": %s\n", SERVER_IFNOTNULL(pdata->server), pdata->stream_name, pa_strerror(pa_context_errno(pdata->context))); + goto fail; + } + log(LOG_INFO, "pulse: %s: stream \"%s\" connected\n", SERVER_IFNOTNULL(pdata->server), pdata->stream_name); + PA_LOOP_UNLOCK(mainloop); + return stream; fail: - PA_LOOP_UNLOCK(mainloop); - return NULL; + PA_LOOP_UNLOCK(mainloop); + return NULL; } -static void pulse_setup_streams(pulse_data *pdata) { - const pa_sample_spec ss = { +static void pulse_setup_streams(pulse_data* pdata) { + const pa_sample_spec ss = { #if __cplusplus >= 199711L - .format = PA_SAMPLE_FLOAT32LE, - .rate = WAVE_RATE, - .channels = 1 -#else // for g++ 4.6 (eg. Raspbian Wheezy) - PA_SAMPLE_FLOAT32LE, WAVE_RATE, 1 + .format = PA_SAMPLE_FLOAT32LE, + .rate = WAVE_RATE, + .channels = 1 +#else // for g++ 4.6 (eg. Raspbian Wheezy) + PA_SAMPLE_FLOAT32LE, + WAVE_RATE, + 1 #endif /* __cplusplus */ - }; - pa_channel_map_init_mono(&pdata->lmap); - pdata->lmap.map[0] = (pdata->mode == MM_STEREO ? PA_CHANNEL_POSITION_LEFT : PA_CHANNEL_POSITION_MONO); - if(!(pdata->left = pulse_setup_stream(pdata, &ss, &pdata->lmap, NULL))) - goto fail; - if(pdata->mode == MM_STEREO) { - pa_channel_map_init_mono(&pdata->rmap); - pdata->rmap.map[0] = PA_CHANNEL_POSITION_RIGHT; - if(!(pdata->right = pulse_setup_stream(pdata, &ss, &pdata->rmap, pdata->left))) - goto fail; - } - return; + }; + pa_channel_map_init_mono(&pdata->lmap); + pdata->lmap.map[0] = (pdata->mode == MM_STEREO ? PA_CHANNEL_POSITION_LEFT : PA_CHANNEL_POSITION_MONO); + if (!(pdata->left = pulse_setup_stream(pdata, &ss, &pdata->lmap, NULL))) + goto fail; + if (pdata->mode == MM_STEREO) { + pa_channel_map_init_mono(&pdata->rmap); + pdata->rmap.map[0] = PA_CHANNEL_POSITION_RIGHT; + if (!(pdata->right = pulse_setup_stream(pdata, &ss, &pdata->rmap, pdata->left))) + goto fail; + } + return; fail: - pulse_shutdown(pdata); + pulse_shutdown(pdata); } -static void pulse_ctx_state_cb(pa_context *c, void *userdata) { - pulse_data *pdata = (pulse_data *)userdata; - switch (pa_context_get_state(c)) { - case PA_CONTEXT_READY: - pulse_setup_streams(pdata); - break; - case PA_CONTEXT_TERMINATED: - break; - case PA_CONTEXT_FAILED: - log(LOG_ERR, "pulse: %s: connection failed: %s\n", SERVER_IFNOTNULL(pdata->server), - pa_strerror(pa_context_errno(pdata->context))); - pulse_shutdown(pdata); - break; - case PA_CONTEXT_CONNECTING: - log(LOG_INFO, "pulse: %s: connecting...\n", SERVER_IFNOTNULL(pdata->server)); - break; - case PA_CONTEXT_UNCONNECTED: - case PA_CONTEXT_AUTHORIZING: - case PA_CONTEXT_SETTING_NAME: - break; - } +static void pulse_ctx_state_cb(pa_context* c, void* userdata) { + pulse_data* pdata = (pulse_data*)userdata; + switch (pa_context_get_state(c)) { + case PA_CONTEXT_READY: + pulse_setup_streams(pdata); + break; + case PA_CONTEXT_TERMINATED: + break; + case PA_CONTEXT_FAILED: + log(LOG_ERR, "pulse: %s: connection failed: %s\n", SERVER_IFNOTNULL(pdata->server), pa_strerror(pa_context_errno(pdata->context))); + pulse_shutdown(pdata); + break; + case PA_CONTEXT_CONNECTING: + log(LOG_INFO, "pulse: %s: connecting...\n", SERVER_IFNOTNULL(pdata->server)); + break; + case PA_CONTEXT_UNCONNECTED: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + break; + } } void pulse_init() { - if(!mainloop && !(mainloop = pa_threaded_mainloop_new())) { - cerr<<"Failed to initialize PulseAudio main loop - aborting\n"; - error(); - } + if (!mainloop && !(mainloop = pa_threaded_mainloop_new())) { + cerr << "Failed to initialize PulseAudio main loop - aborting\n"; + error(); + } } -int pulse_setup(pulse_data *pdata, mix_modes mixmode) { - if(!(pdata->context = pa_context_new(pa_threaded_mainloop_get_api(mainloop), pdata->name))) { - log(LOG_ERR, "%s", "pulse: failed to create context\n"); - return -1; - } - pdata->mode = mixmode; - PA_LOOP_LOCK(mainloop); - int ret = 0; - pa_context_set_state_callback(pdata->context, &pulse_ctx_state_cb, pdata); - if(pa_context_connect(pdata->context, pdata->server, PA_CONTEXT_NOFLAGS, NULL) < 0) { - log(LOG_WARNING, "pulse: %s: failed to connect: %s\n", SERVER_IFNOTNULL(pdata->server), - pa_strerror(pa_context_errno(pdata->context))); - // Don't clean up things here, context state is now set to PA_CONTEXT_FAILED, - // so pulse_ctx_state_cb will take care of that. - ret = -1; - } - PA_LOOP_UNLOCK(mainloop); - return ret; +int pulse_setup(pulse_data* pdata, mix_modes mixmode) { + if (!(pdata->context = pa_context_new(pa_threaded_mainloop_get_api(mainloop), pdata->name))) { + log(LOG_ERR, "%s", "pulse: failed to create context\n"); + return -1; + } + pdata->mode = mixmode; + PA_LOOP_LOCK(mainloop); + int ret = 0; + pa_context_set_state_callback(pdata->context, &pulse_ctx_state_cb, pdata); + if (pa_context_connect(pdata->context, pdata->server, PA_CONTEXT_NOFLAGS, NULL) < 0) { + log(LOG_WARNING, "pulse: %s: failed to connect: %s\n", SERVER_IFNOTNULL(pdata->server), pa_strerror(pa_context_errno(pdata->context))); + // Don't clean up things here, context state is now set to PA_CONTEXT_FAILED, + // so pulse_ctx_state_cb will take care of that. + ret = -1; + } + PA_LOOP_UNLOCK(mainloop); + return ret; } void pulse_start() { - if(!mainloop) return; - PA_LOOP_LOCK(mainloop); - pa_threaded_mainloop_start(mainloop); - PA_LOOP_UNLOCK(mainloop); + if (!mainloop) + return; + PA_LOOP_LOCK(mainloop); + pa_threaded_mainloop_start(mainloop); + PA_LOOP_UNLOCK(mainloop); } -static int pulse_write_single_stream(pa_stream *stream, pulse_data *pdata, const float *data, size_t len, bool is_master) { - pa_usec_t latency; - int ret = -1; - int lret; - - PA_LOOP_LOCK(mainloop); - if(!stream || pa_stream_get_state(stream) != PA_STREAM_READY) - goto end; - - if(is_master) { /* latency info is only meaningful for master stream) */ - lret = pa_stream_get_latency(stream, &latency, NULL); - if(lret < 0) { - log(LOG_WARNING, "pulse: %s: failed to get latency info for stream \"%s\" (error is: %s), disconnecting\n", - SERVER_IFNOTNULL(pdata->server), pdata->stream_name, pa_strerror(lret)); - goto end; - } - if(latency > PULSE_STREAM_LATENCY_LIMIT) { - log(LOG_INFO, "pulse: %s: exceeded max backlog for stream \"%s\", disconnecting\n", - SERVER_IFNOTNULL(pdata->server), pdata->stream_name); - goto end; - } - debug_bulk_print("pulse: %s: stream=\"%s\" lret=%d latency=%f ms\n", - SERVER_IFNOTNULL(pdata->server), pdata->stream_name, lret, (float)latency / 1000.0f); - } - if(pa_stream_write(stream, data, len, NULL, 0LL, PA_SEEK_RELATIVE) < 0) { - log(LOG_WARNING, "pulse: %s: could not write to stream \"%s\", disconnecting\n", - SERVER_IFNOTNULL(pdata->server), pdata->stream_name); - goto end; - } - ret = 0; +static int pulse_write_single_stream(pa_stream* stream, pulse_data* pdata, const float* data, size_t len, bool is_master) { + pa_usec_t latency; + int ret = -1; + int lret; + + PA_LOOP_LOCK(mainloop); + if (!stream || pa_stream_get_state(stream) != PA_STREAM_READY) + goto end; + + if (is_master) { /* latency info is only meaningful for master stream) */ + lret = pa_stream_get_latency(stream, &latency, NULL); + if (lret < 0) { + log(LOG_WARNING, "pulse: %s: failed to get latency info for stream \"%s\" (error is: %s), disconnecting\n", SERVER_IFNOTNULL(pdata->server), pdata->stream_name, pa_strerror(lret)); + goto end; + } + if (latency > PULSE_STREAM_LATENCY_LIMIT) { + log(LOG_INFO, "pulse: %s: exceeded max backlog for stream \"%s\", disconnecting\n", SERVER_IFNOTNULL(pdata->server), pdata->stream_name); + goto end; + } + debug_bulk_print("pulse: %s: stream=\"%s\" lret=%d latency=%f ms\n", SERVER_IFNOTNULL(pdata->server), pdata->stream_name, lret, (float)latency / 1000.0f); + } + if (pa_stream_write(stream, data, len, NULL, 0LL, PA_SEEK_RELATIVE) < 0) { + log(LOG_WARNING, "pulse: %s: could not write to stream \"%s\", disconnecting\n", SERVER_IFNOTNULL(pdata->server), pdata->stream_name); + goto end; + } + ret = 0; end: - PA_LOOP_UNLOCK(mainloop); - return ret; + PA_LOOP_UNLOCK(mainloop); + return ret; } -void pulse_write_stream(pulse_data *pdata, mix_modes mode, const float *data_left, const float *data_right, size_t len) { - PA_LOOP_LOCK(mainloop); - if(!pdata->context || pa_context_get_state(pdata->context) != PA_CONTEXT_READY) - goto end; - if(pulse_write_single_stream(pdata->left, pdata, data_left, len, true) < 0) - goto fail; - if(mode == MM_STEREO && pulse_write_single_stream(pdata->right, pdata, data_right, len, false) < 0) - goto fail; - goto end; +void pulse_write_stream(pulse_data* pdata, mix_modes mode, const float* data_left, const float* data_right, size_t len) { + PA_LOOP_LOCK(mainloop); + if (!pdata->context || pa_context_get_state(pdata->context) != PA_CONTEXT_READY) + goto end; + if (pulse_write_single_stream(pdata->left, pdata, data_left, len, true) < 0) + goto fail; + if (mode == MM_STEREO && pulse_write_single_stream(pdata->right, pdata, data_right, len, false) < 0) + goto fail; + goto end; fail: - pulse_shutdown(pdata); + pulse_shutdown(pdata); end: - PA_LOOP_UNLOCK(mainloop); - return; + PA_LOOP_UNLOCK(mainloop); + return; } diff --git a/src/rtl_airband.cpp b/src/rtl_airband.cpp index 9c06a52..0cfb8e3 100644 --- a/src/rtl_airband.cpp +++ b/src/rtl_airband.cpp @@ -27,39 +27,39 @@ // From this point we may safely assume that WITH_BCM_VC implies __arm__ #ifdef WITH_BCM_VC -#include "hello_fft/mailbox.h" #include "hello_fft/gpu_fft.h" +#include "hello_fft/mailbox.h" #endif /* WITH_BCM_VC */ -#include +#include +#include +#include #include -#include -#include +#include +#include // uint8_t #include #include -#include +#include #include +#include +#include +#include #include +#include +#include +#include #include #include -#include -#include -#include #include -#include -#include #include +#include #include +#include #include -#include // uint8_t -#include -#include -#include -#include #include "input-common.h" +#include "logging.h" #include "rtl_airband.h" #include "squelch.h" -#include "logging.h" #ifdef WITH_PROFILING #include "gperftools/profiler.h" @@ -72,1121 +72,1095 @@ device_t* devices; mixer_t* mixers; int device_count, mixer_count; static int devices_running = 0; -int tui = 0; // do not display textual user interface +int tui = 0; // do not display textual user interface int shout_metadata_delay = 3; volatile int do_exit = 0; bool use_localtime = false; bool multiple_demod_threads = false; bool multiple_output_threads = false; bool log_scan_activity = false; -char *stats_filepath = NULL; +char* stats_filepath = NULL; size_t fft_size_log = DEFAULT_FFT_SIZE_LOG; size_t fft_size = 1 << fft_size_log; #ifdef NFM -float alpha = exp(-1.0f/(WAVE_RATE * 2e-4)); -enum fm_demod_algo { - FM_FAST_ATAN2, - FM_QUADRI_DEMOD -}; +float alpha = exp(-1.0f / (WAVE_RATE * 2e-4)); +enum fm_demod_algo { FM_FAST_ATAN2, FM_QUADRI_DEMOD }; enum fm_demod_algo fm_demod = FM_FAST_ATAN2; #endif /* NFM */ #ifdef DEBUG -char *debug_path; +char* debug_path; #endif /* DEBUG */ void sighandler(int sig) { - log(LOG_NOTICE, "Got signal %d, exiting\n", sig); - do_exit = 1; + log(LOG_NOTICE, "Got signal %d, exiting\n", sig); + do_exit = 1; } - void* controller_thread(void* params) { - device_t *dev = (device_t*)params; - int i = 0; - int consecutive_squelch_off = 0; - int new_centerfreq = 0; - struct timeval tv; - - if(dev->channels[0].freq_count < 2) return 0; - while(!do_exit) { - SLEEP(200); - if(dev->channels[0].axcindicate == NO_SIGNAL) { - if(consecutive_squelch_off < 10) { - consecutive_squelch_off++; - } else { - i++; i %= dev->channels[0].freq_count; - dev->channels[0].freq_idx = i; - new_centerfreq = dev->channels[0].freqlist[i].frequency + 20 * (double)(dev->input->sample_rate / fft_size); - if(input_set_centerfreq(dev->input, new_centerfreq) < 0) { - break; - } - } - } else { - if(consecutive_squelch_off == 10) { - if(log_scan_activity) - log(LOG_INFO, "Activity on %7.3f MHz\n", dev->channels[0].freqlist[i].frequency / 1000000.0); - if(i != dev->last_frequency) { -// squelch has just opened on a new frequency - we might need to update outputs' metadata - gettimeofday(&tv, NULL); - tag_queue_put(dev, i, tv); - dev->last_frequency = i; - } - } - consecutive_squelch_off = 0; - } - } - return 0; + device_t* dev = (device_t*)params; + int i = 0; + int consecutive_squelch_off = 0; + int new_centerfreq = 0; + struct timeval tv; + + if (dev->channels[0].freq_count < 2) + return 0; + while (!do_exit) { + SLEEP(200); + if (dev->channels[0].axcindicate == NO_SIGNAL) { + if (consecutive_squelch_off < 10) { + consecutive_squelch_off++; + } else { + i++; + i %= dev->channels[0].freq_count; + dev->channels[0].freq_idx = i; + new_centerfreq = dev->channels[0].freqlist[i].frequency + 20 * (double)(dev->input->sample_rate / fft_size); + if (input_set_centerfreq(dev->input, new_centerfreq) < 0) { + break; + } + } + } else { + if (consecutive_squelch_off == 10) { + if (log_scan_activity) + log(LOG_INFO, "Activity on %7.3f MHz\n", dev->channels[0].freqlist[i].frequency / 1000000.0); + if (i != dev->last_frequency) { + // squelch has just opened on a new frequency - we might need to update outputs' metadata + gettimeofday(&tv, NULL); + tag_queue_put(dev, i, tv); + dev->last_frequency = i; + } + } + consecutive_squelch_off = 0; + } + } + return 0; } -void multiply(float ar, float aj, float br, float bj, float *cr, float *cj) -{ - *cr = ar*br - aj*bj; - *cj = aj*br + ar*bj; +void multiply(float ar, float aj, float br, float bj, float* cr, float* cj) { + *cr = ar * br - aj * bj; + *cj = aj * br + ar * bj; } #ifdef NFM -float fast_atan2(float y, float x) -{ - float yabs, angle; - float pi4=M_PI_4, pi34=3*M_PI_4; - if (x==0.0f && y==0.0f) { - return 0; - } - yabs = y; - if (yabs < 0.0f) { - yabs = -yabs; - } - if (x >= 0.0f) { - angle = pi4 - pi4 * (x-yabs) / (x+yabs); - } else { - angle = pi34 - pi4 * (x+yabs) / (yabs-x); - } - if (y < 0.0f) { - return -angle; - } - return angle; +float fast_atan2(float y, float x) { + float yabs, angle; + float pi4 = M_PI_4, pi34 = 3 * M_PI_4; + if (x == 0.0f && y == 0.0f) { + return 0; + } + yabs = y; + if (yabs < 0.0f) { + yabs = -yabs; + } + if (x >= 0.0f) { + angle = pi4 - pi4 * (x - yabs) / (x + yabs); + } else { + angle = pi34 - pi4 * (x + yabs) / (yabs - x); + } + if (y < 0.0f) { + return -angle; + } + return angle; } -float polar_disc_fast(float ar, float aj, float br, float bj) -{ - float cr, cj; - multiply(ar, aj, br, -bj, &cr, &cj); - return (float)(fast_atan2(cj, cr) * M_1_PI); +float polar_disc_fast(float ar, float aj, float br, float bj) { + float cr, cj; + multiply(ar, aj, br, -bj, &cr, &cj); + return (float)(fast_atan2(cj, cr) * M_1_PI); } float fm_quadri_demod(float ar, float aj, float br, float bj) { - return (float)((br*aj - ar*bj)/(ar*ar + aj*aj + 1.0f) * M_1_PI); + return (float)((br * aj - ar * bj) / (ar * ar + aj * aj + 1.0f) * M_1_PI); } #endif /* NFM */ -class AFC -{ - const status _prev_axcindicate; +class AFC { + const status _prev_axcindicate; #ifdef WITH_BCM_VC - float square(const GPU_FFT_COMPLEX *fft_results, size_t index) - { - return fft_results[index].re * fft_results[index].re + fft_results[index].im * fft_results[index].im; - } + float square(const GPU_FFT_COMPLEX* fft_results, size_t index) { + return fft_results[index].re * fft_results[index].re + fft_results[index].im * fft_results[index].im; + } #else - float square(const fftwf_complex *fft_results, size_t index) - { - return fft_results[index][0] * fft_results[index][0] + fft_results[index][1] * fft_results[index][1]; - } + float square(const fftwf_complex* fft_results, size_t index) { + return fft_results[index][0] * fft_results[index][0] + fft_results[index][1] * fft_results[index][1]; + } #endif /* WITH_BCM_VC */ - template - size_t check(const FFT_RESULTS* fft_results, const size_t base, const float base_value, unsigned char afc) - { - float threshold = 0; - size_t bin; - for (bin = base;; bin+= STEP) { - if (STEP < 0) { - if (bin < -STEP) - break; - - } else if ( (size_t)(bin + STEP) >= fft_size) - break; - - const float value = square(fft_results, (size_t)(bin + STEP)); - if (value <= base_value) - break; - - if (base == (size_t)bin) { - threshold = (value - base_value) / (float)afc; - } else { - if ((value - base_value) < threshold) - break; - - threshold+= threshold / 10.0; - } - } - return bin; - } - -public: - AFC(device_t* dev, int index) : _prev_axcindicate(dev->channels[index].axcindicate) - { - } - - template - void finalize(device_t* dev, int index, const FFT_RESULTS* fft_results) - { - channel_t *channel = &dev->channels[index]; - if (channel->afc==0) - return; - - const char axcindicate = channel->axcindicate; - if (axcindicate != NO_SIGNAL && _prev_axcindicate == NO_SIGNAL) { - const size_t base = dev->base_bins[index]; - const float base_value = square(fft_results, base); - size_t bin = check(fft_results, base, base_value, channel->afc); - if (bin == base) - bin = check(fft_results, base, base_value, channel->afc); - - if (dev->bins[index] != bin) { + template + size_t check(const FFT_RESULTS* fft_results, const size_t base, const float base_value, unsigned char afc) { + float threshold = 0; + size_t bin; + for (bin = base;; bin += STEP) { + if (STEP < 0) { + if (bin < -STEP) + break; + + } else if ((size_t)(bin + STEP) >= fft_size) + break; + + const float value = square(fft_results, (size_t)(bin + STEP)); + if (value <= base_value) + break; + + if (base == (size_t)bin) { + threshold = (value - base_value) / (float)afc; + } else { + if ((value - base_value) < threshold) + break; + + threshold += threshold / 10.0; + } + } + return bin; + } + + public: + AFC(device_t* dev, int index) : _prev_axcindicate(dev->channels[index].axcindicate) {} + + template + void finalize(device_t* dev, int index, const FFT_RESULTS* fft_results) { + channel_t* channel = &dev->channels[index]; + if (channel->afc == 0) + return; + + const char axcindicate = channel->axcindicate; + if (axcindicate != NO_SIGNAL && _prev_axcindicate == NO_SIGNAL) { + const size_t base = dev->base_bins[index]; + const float base_value = square(fft_results, base); + size_t bin = check(fft_results, base, base_value, channel->afc); + if (bin == base) + bin = check(fft_results, base, base_value, channel->afc); + + if (dev->bins[index] != bin) { #ifdef AFC_LOGGING - log(LOG_INFO, "AFC device=%d channel=%d: base=%zu prev=%zu now=%zu\n", dev->device, index, base, dev->bins[index], bin); + log(LOG_INFO, "AFC device=%d channel=%d: base=%zu prev=%zu now=%zu\n", dev->device, index, base, dev->bins[index], bin); #endif /* AFC_LOGGING */ - dev->bins[index] = bin; - if ( bin > base ) - channel->axcindicate = AFC_UP; - else if ( bin < base ) - channel->axcindicate = AFC_DOWN; - } - } - else if (axcindicate == NO_SIGNAL && _prev_axcindicate != NO_SIGNAL) - dev->bins[index] = dev->base_bins[index]; - } + dev->bins[index] = bin; + if (bin > base) + channel->axcindicate = AFC_UP; + else if (bin < base) + channel->axcindicate = AFC_DOWN; + } + } else if (axcindicate == NO_SIGNAL && _prev_axcindicate != NO_SIGNAL) + dev->bins[index] = dev->base_bins[index]; + } }; -void init_demod(demod_params_t *params, Signal *signal, int device_start, int device_end) { - assert(params != NULL); - assert(signal != NULL); +void init_demod(demod_params_t* params, Signal* signal, int device_start, int device_end) { + assert(params != NULL); + assert(signal != NULL); - params->mp3_signal = signal; - params->device_start = device_start; - params->device_end = device_end; + params->mp3_signal = signal; + params->device_start = device_start; + params->device_end = device_end; #ifndef WITH_BCM_VC - params->fftin = fftwf_alloc_complex(fft_size); - params->fftout = fftwf_alloc_complex(fft_size); - params->fft = fftwf_plan_dft_1d(fft_size, params->fftin, params->fftout, FFTW_FORWARD, FFTW_MEASURE); + params->fftin = fftwf_alloc_complex(fft_size); + params->fftout = fftwf_alloc_complex(fft_size); + params->fft = fftwf_plan_dft_1d(fft_size, params->fftin, params->fftout, FFTW_FORWARD, FFTW_MEASURE); #endif /* WITH_BCM_VC */ } -void init_output(output_params_t *params, int device_start, int device_end, int mixer_start, int mixer_end) { - assert(params != NULL); +void init_output(output_params_t* params, int device_start, int device_end, int mixer_start, int mixer_end) { + assert(params != NULL); - params->mp3_signal = new Signal; - params->device_start = device_start; - params->device_end = device_end; - params->mixer_start = mixer_start; - params->mixer_end = mixer_end; + params->mp3_signal = new Signal; + params->device_start = device_start; + params->device_end = device_end; + params->mixer_start = mixer_start; + params->mixer_end = mixer_end; } -int next_device(demod_params_t *params, int current) { - current++; - if (current < params->device_end) { - return current; - } - return params->device_start; +int next_device(demod_params_t* params, int current) { + current++; + if (current < params->device_end) { + return current; + } + return params->device_start; } -void *demodulate(void *params) { - assert(params != NULL); - demod_params_t *demod_params = (demod_params_t *) params; +void* demodulate(void* params) { + assert(params != NULL); + demod_params_t* demod_params = (demod_params_t*)params; - debug_print("Starting demod thread, devices %d:%d, signal %p\n", demod_params->device_start, demod_params->device_end, demod_params->mp3_signal); + debug_print("Starting demod thread, devices %d:%d, signal %p\n", demod_params->device_start, demod_params->device_end, demod_params->mp3_signal); - // initialize fft engine + // initialize fft engine #ifdef WITH_BCM_VC - int mb = mbox_open(); - struct GPU_FFT *fft; - int ret = gpu_fft_prepare(mb, fft_size_log, GPU_FFT_FWD, FFT_BATCH, &fft); - switch (ret) { - case -1: - log(LOG_CRIT, "Unable to enable V3D. Please check your firmware is up to date.\n"); - error(); - break; - case -2: - log(LOG_CRIT, "log2_N=%d not supported. Try between 8 and 17.\n", fft_size_log); - error(); - break; - case -3: - log(LOG_CRIT, "Out of memory. Try a smaller batch or increase GPU memory.\n"); - error(); - break; - } + int mb = mbox_open(); + struct GPU_FFT* fft; + int ret = gpu_fft_prepare(mb, fft_size_log, GPU_FFT_FWD, FFT_BATCH, &fft); + switch (ret) { + case -1: + log(LOG_CRIT, "Unable to enable V3D. Please check your firmware is up to date.\n"); + error(); + break; + case -2: + log(LOG_CRIT, "log2_N=%d not supported. Try between 8 and 17.\n", fft_size_log); + error(); + break; + case -3: + log(LOG_CRIT, "Out of memory. Try a smaller batch or increase GPU memory.\n"); + error(); + break; + } #else - fftwf_complex* fftin = demod_params->fftin; - fftwf_complex* fftout = demod_params->fftout; + fftwf_complex* fftin = demod_params->fftin; + fftwf_complex* fftout = demod_params->fftout; #endif /* WITH_BCM_VC */ - float ALIGNED32 levels_u8[256], levels_s8[256]; - float *levels_ptr = NULL; + float ALIGNED32 levels_u8[256], levels_s8[256]; + float* levels_ptr = NULL; - for (int i=0; i<256; i++) { - levels_u8[i] = (i-127.5f)/127.5f; - } - for (int16_t i=-127; i<128; i++) { - levels_s8[(uint8_t)i] = i/128.0f; - } + for (int i = 0; i < 256; i++) { + levels_u8[i] = (i - 127.5f) / 127.5f; + } + for (int16_t i = -127; i < 128; i++) { + levels_s8[(uint8_t)i] = i / 128.0f; + } - // initialize fft window - // blackman 7 - // the whole matrix is computed + // initialize fft window + // blackman 7 + // the whole matrix is computed #ifdef WITH_BCM_VC - float ALIGNED32 window[fft_size * 2]; + float ALIGNED32 window[fft_size * 2]; #else - float ALIGNED32 window[fft_size]; + float ALIGNED32 window[fft_size]; #endif /* WITH_BCM_VC */ - const double a0 = 0.27105140069342f; - const double a1 = 0.43329793923448f; const double a2 = 0.21812299954311f; - const double a3 = 0.06592544638803f; const double a4 = 0.01081174209837f; - const double a5 = 0.00077658482522f; const double a6 = 0.00001388721735f; - - for (size_t i = 0; i < fft_size; i++) { - double x = a0 - (a1 * cos((2.0 * M_PI * i) / (fft_size-1))) - + (a2 * cos((4.0 * M_PI * i) / (fft_size - 1))) - - (a3 * cos((6.0 * M_PI * i) / (fft_size - 1))) - + (a4 * cos((8.0 * M_PI * i) / (fft_size - 1))) - - (a5 * cos((10.0 * M_PI * i) / (fft_size - 1))) - + (a6 * cos((12.0 * M_PI * i) / (fft_size - 1))); + const double a0 = 0.27105140069342f; + const double a1 = 0.43329793923448f; + const double a2 = 0.21812299954311f; + const double a3 = 0.06592544638803f; + const double a4 = 0.01081174209837f; + const double a5 = 0.00077658482522f; + const double a6 = 0.00001388721735f; + + for (size_t i = 0; i < fft_size; i++) { + double x = a0 - (a1 * cos((2.0 * M_PI * i) / (fft_size - 1))) + (a2 * cos((4.0 * M_PI * i) / (fft_size - 1))) - (a3 * cos((6.0 * M_PI * i) / (fft_size - 1))) + + (a4 * cos((8.0 * M_PI * i) / (fft_size - 1))) - (a5 * cos((10.0 * M_PI * i) / (fft_size - 1))) + (a6 * cos((12.0 * M_PI * i) / (fft_size - 1))); #ifdef WITH_BCM_VC - window[i * 2] = window[i * 2 + 1] = (float)x; + window[i * 2] = window[i * 2 + 1] = (float)x; #else - window[i] = (float)x; + window[i] = (float)x; #endif /* WITH_BCM_VC */ - } + } #ifdef DEBUG - struct timeval ts, te; - gettimeofday(&ts, NULL); + struct timeval ts, te; + gettimeofday(&ts, NULL); #endif /* DEBUG */ - size_t available; - int device_num = demod_params->device_start; - while (true) { - - if(do_exit) { + size_t available; + int device_num = demod_params->device_start; + while (true) { + if (do_exit) { #ifdef WITH_BCM_VC - log(LOG_INFO, "Freeing GPU memory\n"); - gpu_fft_release(fft); + log(LOG_INFO, "Freeing GPU memory\n"); + gpu_fft_release(fft); #endif /* WITH_BCM_VC */ - return NULL; - } - - device_t* dev = devices + device_num; - - pthread_mutex_lock(&dev->input->buffer_lock); - if(dev->input->bufe >= dev->input->bufs) - available = dev->input->bufe - dev->input->bufs; - else - available = dev->input->buf_size - dev->input->bufs + dev->input->bufe; - pthread_mutex_unlock(&dev->input->buffer_lock); - - if(devices_running == 0) { - log(LOG_ERR, "All receivers failed, exiting\n"); - do_exit = 1; - continue; - } - - if (dev->input->state != INPUT_RUNNING) { - if(dev->input->state == INPUT_FAILED) { - dev->input->state = INPUT_DISABLED; - disable_device_outputs(dev); - devices_running--; - } - device_num = next_device(demod_params, device_num); - continue; - } - - // number of input bytes per output wave sample (x 2 for I and Q) - size_t bps = 2 * dev->input->bytes_per_sample * (size_t)round((double)dev->input->sample_rate / (double)WAVE_RATE); - if (available < bps * FFT_BATCH + fft_size * dev->input->bytes_per_sample * 2) { - // move to next device - device_num = next_device(demod_params, device_num); - SLEEP(10); - continue; - } - - if(dev->input->sfmt == SFMT_S16) { - float const scale = 1.0f / dev->input->fullscale; + return NULL; + } + + device_t* dev = devices + device_num; + + pthread_mutex_lock(&dev->input->buffer_lock); + if (dev->input->bufe >= dev->input->bufs) + available = dev->input->bufe - dev->input->bufs; + else + available = dev->input->buf_size - dev->input->bufs + dev->input->bufe; + pthread_mutex_unlock(&dev->input->buffer_lock); + + if (devices_running == 0) { + log(LOG_ERR, "All receivers failed, exiting\n"); + do_exit = 1; + continue; + } + + if (dev->input->state != INPUT_RUNNING) { + if (dev->input->state == INPUT_FAILED) { + dev->input->state = INPUT_DISABLED; + disable_device_outputs(dev); + devices_running--; + } + device_num = next_device(demod_params, device_num); + continue; + } + + // number of input bytes per output wave sample (x 2 for I and Q) + size_t bps = 2 * dev->input->bytes_per_sample * (size_t)round((double)dev->input->sample_rate / (double)WAVE_RATE); + if (available < bps * FFT_BATCH + fft_size * dev->input->bytes_per_sample * 2) { + // move to next device + device_num = next_device(demod_params, device_num); + SLEEP(10); + continue; + } + + if (dev->input->sfmt == SFMT_S16) { + float const scale = 1.0f / dev->input->fullscale; #ifdef WITH_BCM_VC - struct GPU_FFT_COMPLEX *ptr = fft->in; - for(size_t b = 0; b < FFT_BATCH; b++, ptr += fft->step) { - short *buf2 = (short *)(dev->input->buffer + dev->input->bufs + b * bps); - for(size_t i = 0; i < fft_size; i++, buf2 += 2) { - ptr[i].re = scale * (float)buf2[0] * window[i*2]; - ptr[i].im = scale * (float)buf2[1] * window[i*2]; - } - } + struct GPU_FFT_COMPLEX* ptr = fft->in; + for (size_t b = 0; b < FFT_BATCH; b++, ptr += fft->step) { + short* buf2 = (short*)(dev->input->buffer + dev->input->bufs + b * bps); + for (size_t i = 0; i < fft_size; i++, buf2 += 2) { + ptr[i].re = scale * (float)buf2[0] * window[i * 2]; + ptr[i].im = scale * (float)buf2[1] * window[i * 2]; + } + } #else - short *buf2 = (short *)(dev->input->buffer + dev->input->bufs); - for(size_t i = 0; i < fft_size; i++, buf2 += 2) { - fftin[i][0] = scale * (float)buf2[0] * window[i]; - fftin[i][1] = scale * (float)buf2[1] * window[i]; - } + short* buf2 = (short*)(dev->input->buffer + dev->input->bufs); + for (size_t i = 0; i < fft_size; i++, buf2 += 2) { + fftin[i][0] = scale * (float)buf2[0] * window[i]; + fftin[i][1] = scale * (float)buf2[1] * window[i]; + } #endif /* WITH_BCM_VC */ - } else if(dev->input->sfmt == SFMT_F32) { - float const scale = 1.0f / dev->input->fullscale; + } else if (dev->input->sfmt == SFMT_F32) { + float const scale = 1.0f / dev->input->fullscale; #ifdef WITH_BCM_VC - struct GPU_FFT_COMPLEX *ptr = fft->in; - for(size_t b = 0; b < FFT_BATCH; b++, ptr += fft->step) { - float *buf2 = (float *)(dev->input->buffer + dev->input->bufs + b * bps); - for(size_t i = 0; i < fft_size; i++, buf2 += 2) { - ptr[i].re = scale * buf2[0] * window[i*2]; - ptr[i].im = scale * buf2[1] * window[i*2]; - } - } -#else // WITH_BCM_VC - float *buf2 = (float *)(dev->input->buffer + dev->input->bufs); - for(size_t i = 0; i < fft_size; i++, buf2 += 2) { - fftin[i][0] = scale * buf2[0] * window[i]; - fftin[i][1] = scale * buf2[1] * window[i]; - } + struct GPU_FFT_COMPLEX* ptr = fft->in; + for (size_t b = 0; b < FFT_BATCH; b++, ptr += fft->step) { + float* buf2 = (float*)(dev->input->buffer + dev->input->bufs + b * bps); + for (size_t i = 0; i < fft_size; i++, buf2 += 2) { + ptr[i].re = scale * buf2[0] * window[i * 2]; + ptr[i].im = scale * buf2[1] * window[i * 2]; + } + } +#else // WITH_BCM_VC + float* buf2 = (float*)(dev->input->buffer + dev->input->bufs); + for (size_t i = 0; i < fft_size; i++, buf2 += 2) { + fftin[i][0] = scale * buf2[0] * window[i]; + fftin[i][1] = scale * buf2[1] * window[i]; + } #endif /* WITH_BCM_VC */ - } else { // S8 or U8 - levels_ptr = (dev->input->sfmt == SFMT_U8 ? levels_u8 : levels_s8); - + } else { // S8 or U8 + levels_ptr = (dev->input->sfmt == SFMT_U8 ? levels_u8 : levels_s8); + #ifdef WITH_BCM_VC - sample_fft_arg sfa = {fft_size / 4, fft->in}; - for (size_t i = 0; i < FFT_BATCH; i++) { - samplefft(&sfa, dev->input->buffer + dev->input->bufs + i * bps, window, levels_ptr); - sfa.dest+= fft->step; - } + sample_fft_arg sfa = {fft_size / 4, fft->in}; + for (size_t i = 0; i < FFT_BATCH; i++) { + samplefft(&sfa, dev->input->buffer + dev->input->bufs + i * bps, window, levels_ptr); + sfa.dest += fft->step; + } #else - unsigned char* buf2 = dev->input->buffer + dev->input->bufs; - for (size_t i = 0; i < fft_size; i++, buf2 += 2) { - fftin[i][0] = levels_ptr[buf2[0]] * window[i]; - fftin[i][1] = levels_ptr[buf2[1]] * window[i]; - } + unsigned char* buf2 = dev->input->buffer + dev->input->bufs; + for (size_t i = 0; i < fft_size; i++, buf2 += 2) { + fftin[i][0] = levels_ptr[buf2[0]] * window[i]; + fftin[i][1] = levels_ptr[buf2[1]] * window[i]; + } #endif /* WITH_BCM_VC */ - } + } #ifdef WITH_BCM_VC - gpu_fft_execute(fft); + gpu_fft_execute(fft); #else - fftwf_execute(demod_params->fft); + fftwf_execute(demod_params->fft); #endif /* WITH_BCM_VC */ #ifdef WITH_BCM_VC - for (int i = 0; i < dev->channel_count; i++) { - float *wavein = dev->channels[i].wavein + dev->waveend; - __builtin_prefetch(wavein, 1); - const int bin = dev->bins[i]; - const GPU_FFT_COMPLEX *fftout = fft->out + bin; - for (int j = 0; j < FFT_BATCH; j++, ++wavein, fftout+= fft->step) - *wavein = sqrtf(fftout->im * fftout->im + fftout->re * fftout->re); - } - for (int j = 0; j < dev->channel_count; j++) { - if(dev->channels[j].needs_raw_iq) { - struct GPU_FFT_COMPLEX *ptr = fft->out; - for (int job = 0; job < FFT_BATCH; job++) { - dev->channels[j].iq_in[2*(dev->waveend+job)] = ptr[dev->bins[j]].re; - dev->channels[j].iq_in[2*(dev->waveend+job)+1] = ptr[dev->bins[j]].im; - ptr += fft->step; - } - } - } + for (int i = 0; i < dev->channel_count; i++) { + float* wavein = dev->channels[i].wavein + dev->waveend; + __builtin_prefetch(wavein, 1); + const int bin = dev->bins[i]; + const GPU_FFT_COMPLEX* fftout = fft->out + bin; + for (int j = 0; j < FFT_BATCH; j++, ++wavein, fftout += fft->step) + *wavein = sqrtf(fftout->im * fftout->im + fftout->re * fftout->re); + } + for (int j = 0; j < dev->channel_count; j++) { + if (dev->channels[j].needs_raw_iq) { + struct GPU_FFT_COMPLEX* ptr = fft->out; + for (int job = 0; job < FFT_BATCH; job++) { + dev->channels[j].iq_in[2 * (dev->waveend + job)] = ptr[dev->bins[j]].re; + dev->channels[j].iq_in[2 * (dev->waveend + job) + 1] = ptr[dev->bins[j]].im; + ptr += fft->step; + } + } + } #else - for (int j = 0; j < dev->channel_count; j++) { - dev->channels[j].wavein[dev->waveend] = - sqrtf(fftout[dev->bins[j]][0] * fftout[dev->bins[j]][0] + fftout[dev->bins[j]][1] * fftout[dev->bins[j]][1]); - if(dev->channels[j].needs_raw_iq) { - dev->channels[j].iq_in[2*dev->waveend] = fftout[dev->bins[j]][0]; - dev->channels[j].iq_in[2*dev->waveend+1] = fftout[dev->bins[j]][1]; - } - } + for (int j = 0; j < dev->channel_count; j++) { + dev->channels[j].wavein[dev->waveend] = sqrtf(fftout[dev->bins[j]][0] * fftout[dev->bins[j]][0] + fftout[dev->bins[j]][1] * fftout[dev->bins[j]][1]); + if (dev->channels[j].needs_raw_iq) { + dev->channels[j].iq_in[2 * dev->waveend] = fftout[dev->bins[j]][0]; + dev->channels[j].iq_in[2 * dev->waveend + 1] = fftout[dev->bins[j]][1]; + } + } #endif /* WITH_BCM_VC */ - dev->waveend += FFT_BATCH; - - if (dev->waveend >= WAVE_BATCH + AGC_EXTRA) { - for (int i = 0; i < dev->channel_count; i++) { - AFC afc(dev, i); - channel_t* channel = dev->channels + i; - freq_t *fparms = channel->freqlist + channel->freq_idx; - - // set to NO_SIGNAL, will be updated to SIGNAL based on squelch below - channel->axcindicate = NO_SIGNAL; - - for (int j = AGC_EXTRA; j < WAVE_BATCH + AGC_EXTRA; j++) { - - float &real = channel->iq_in[2*(j - AGC_EXTRA)]; - float &imag = channel->iq_in[2*(j - AGC_EXTRA)+1]; - - fparms->squelch.process_raw_sample(channel->wavein[j]); - - // If squelch is open / opening and using I/Q, then cleanup the signal and possibly update squelch. - if (fparms->squelch.should_filter_sample() && channel->needs_raw_iq) { - - // remove phase rotation introduced by FFT sliding window - float swf, cwf, re_tmp, im_tmp; - sincosf_lut(channel->dm_phi, &swf, &cwf); - multiply(real, imag, cwf, -swf, &re_tmp, &im_tmp); - channel->dm_phi += channel->dm_dphi; - channel->dm_phi &= 0xffffff; - - // apply lowpass filter, will be a no-op if not configured - fparms->lowpass_filter.apply(re_tmp, im_tmp); - - // update I/Q and wave - real = re_tmp; - imag = im_tmp; - channel->wavein[j] = sqrt(real * real + imag * imag); - - // update squelch post-cleanup - if (fparms->lowpass_filter.enabled()) { - fparms->squelch.process_filtered_sample(channel->wavein[j]); - } - } - - if(fparms->modulation == MOD_AM) { - // if squelch is just opening then bootstrip agcavgfast with prior values of wavein - if (fparms->squelch.first_open_sample()) { - for (int k = j - AGC_EXTRA; k < j; k++) { - if (channel->wavein[k] >= fparms->squelch.squelch_level()) { - fparms->agcavgfast = fparms->agcavgfast * 0.9f + channel->wavein[k] * 0.1f; - } - } - } - // if squelch is just closing then fade out the prior samples of waveout - else if (fparms->squelch.last_open_sample()) { - for (int k = j - AGC_EXTRA + 1; k < j; k++) { - channel->waveout[k] = channel->waveout[k - 1] * 0.94f; - } - } - } - - float &waveout = channel->waveout[j]; - - // If squelch sees power then do modulation-specific processing - if (fparms->squelch.should_process_audio()) { - if(fparms->modulation == MOD_AM) { - if( channel->wavein[j] > fparms->squelch.squelch_level() ) { - fparms->agcavgfast = fparms->agcavgfast * 0.995f + channel->wavein[j] * 0.005f; - } - - waveout = (channel->wavein[j - AGC_EXTRA] - fparms->agcavgfast) / (fparms->agcavgfast * 1.5f); - if (abs(waveout) > 0.8f) { - waveout *= 0.85f; - fparms->agcavgfast *= 1.15f; - } - } + dev->waveend += FFT_BATCH; + + if (dev->waveend >= WAVE_BATCH + AGC_EXTRA) { + for (int i = 0; i < dev->channel_count; i++) { + AFC afc(dev, i); + channel_t* channel = dev->channels + i; + freq_t* fparms = channel->freqlist + channel->freq_idx; + + // set to NO_SIGNAL, will be updated to SIGNAL based on squelch below + channel->axcindicate = NO_SIGNAL; + + for (int j = AGC_EXTRA; j < WAVE_BATCH + AGC_EXTRA; j++) { + float& real = channel->iq_in[2 * (j - AGC_EXTRA)]; + float& imag = channel->iq_in[2 * (j - AGC_EXTRA) + 1]; + + fparms->squelch.process_raw_sample(channel->wavein[j]); + + // If squelch is open / opening and using I/Q, then cleanup the signal and possibly update squelch. + if (fparms->squelch.should_filter_sample() && channel->needs_raw_iq) { + // remove phase rotation introduced by FFT sliding window + float swf, cwf, re_tmp, im_tmp; + sincosf_lut(channel->dm_phi, &swf, &cwf); + multiply(real, imag, cwf, -swf, &re_tmp, &im_tmp); + channel->dm_phi += channel->dm_dphi; + channel->dm_phi &= 0xffffff; + + // apply lowpass filter, will be a no-op if not configured + fparms->lowpass_filter.apply(re_tmp, im_tmp); + + // update I/Q and wave + real = re_tmp; + imag = im_tmp; + channel->wavein[j] = sqrt(real * real + imag * imag); + + // update squelch post-cleanup + if (fparms->lowpass_filter.enabled()) { + fparms->squelch.process_filtered_sample(channel->wavein[j]); + } + } + + if (fparms->modulation == MOD_AM) { + // if squelch is just opening then bootstrip agcavgfast with prior values of wavein + if (fparms->squelch.first_open_sample()) { + for (int k = j - AGC_EXTRA; k < j; k++) { + if (channel->wavein[k] >= fparms->squelch.squelch_level()) { + fparms->agcavgfast = fparms->agcavgfast * 0.9f + channel->wavein[k] * 0.1f; + } + } + } + // if squelch is just closing then fade out the prior samples of waveout + else if (fparms->squelch.last_open_sample()) { + for (int k = j - AGC_EXTRA + 1; k < j; k++) { + channel->waveout[k] = channel->waveout[k - 1] * 0.94f; + } + } + } + + float& waveout = channel->waveout[j]; + + // If squelch sees power then do modulation-specific processing + if (fparms->squelch.should_process_audio()) { + if (fparms->modulation == MOD_AM) { + if (channel->wavein[j] > fparms->squelch.squelch_level()) { + fparms->agcavgfast = fparms->agcavgfast * 0.995f + channel->wavein[j] * 0.005f; + } + + waveout = (channel->wavein[j - AGC_EXTRA] - fparms->agcavgfast) / (fparms->agcavgfast * 1.5f); + if (abs(waveout) > 0.8f) { + waveout *= 0.85f; + fparms->agcavgfast *= 1.15f; + } + } #ifdef NFM - else if(fparms->modulation == MOD_NFM) { - // FM demod - if(fm_demod == FM_FAST_ATAN2) { - waveout = polar_disc_fast(real, imag, channel->pr, channel->pj); - } else if(fm_demod == FM_QUADRI_DEMOD) { - waveout = fm_quadri_demod(real, imag, channel->pr, channel->pj); - } - channel->pr = real; - channel->pj = imag; - - // de-emphasis IIR + DC blocking - fparms->agcavgfast = fparms->agcavgfast * 0.995f + waveout * 0.005f; - waveout -= fparms->agcavgfast; - waveout = waveout * (1.0f - channel->alpha) + channel->prev_waveout * channel->alpha; - - // save off waveout before notch and ampfactor - channel->prev_waveout = waveout; - } + else if (fparms->modulation == MOD_NFM) { + // FM demod + if (fm_demod == FM_FAST_ATAN2) { + waveout = polar_disc_fast(real, imag, channel->pr, channel->pj); + } else if (fm_demod == FM_QUADRI_DEMOD) { + waveout = fm_quadri_demod(real, imag, channel->pr, channel->pj); + } + channel->pr = real; + channel->pj = imag; + + // de-emphasis IIR + DC blocking + fparms->agcavgfast = fparms->agcavgfast * 0.995f + waveout * 0.005f; + waveout -= fparms->agcavgfast; + waveout = waveout * (1.0f - channel->alpha) + channel->prev_waveout * channel->alpha; + + // save off waveout before notch and ampfactor + channel->prev_waveout = waveout; + } #endif /* NFM */ - - // process audio sample for CTCSS, will be no-op if not configured - fparms->squelch.process_audio_sample(waveout); - } - - // If squelch is still open then save samples to output - if (fparms->squelch.is_open()) { - - // apply the notch filter, will be a no-op if not configured - fparms->notch_filter.apply(waveout); - - // apply the ampfactor - waveout *= fparms->ampfactor; - - // make sure the value is between +/- 1 (requirement for libmp3lame) - if (isnan(waveout)) { - waveout = 0.0; - } - else if (waveout > 1.0) { - waveout = 1.0; - } - else if (waveout < -1.0) { - waveout = -1.0; - } - - channel->axcindicate = SIGNAL; - if(channel->has_iq_outputs) { - channel->iq_out[2*(j - AGC_EXTRA)] = real; - channel->iq_out[2*(j - AGC_EXTRA)+1] = imag; - } - - // Squelch is closed - } else { - waveout = 0; - if(channel->has_iq_outputs) { - channel->iq_out[2*(j - AGC_EXTRA)] = 0; - channel->iq_out[2*(j - AGC_EXTRA)+1] = 0; - } - } - } - memmove(channel->wavein, channel->wavein + WAVE_BATCH, (dev->waveend - WAVE_BATCH) * sizeof(float)); - if(channel->needs_raw_iq) { - memmove(channel->iq_in, channel->iq_in + 2 * WAVE_BATCH, (dev->waveend - WAVE_BATCH) * sizeof(float) * 2); - } + + // process audio sample for CTCSS, will be no-op if not configured + fparms->squelch.process_audio_sample(waveout); + } + + // If squelch is still open then save samples to output + if (fparms->squelch.is_open()) { + // apply the notch filter, will be a no-op if not configured + fparms->notch_filter.apply(waveout); + + // apply the ampfactor + waveout *= fparms->ampfactor; + + // make sure the value is between +/- 1 (requirement for libmp3lame) + if (isnan(waveout)) { + waveout = 0.0; + } else if (waveout > 1.0) { + waveout = 1.0; + } else if (waveout < -1.0) { + waveout = -1.0; + } + + channel->axcindicate = SIGNAL; + if (channel->has_iq_outputs) { + channel->iq_out[2 * (j - AGC_EXTRA)] = real; + channel->iq_out[2 * (j - AGC_EXTRA) + 1] = imag; + } + + // Squelch is closed + } else { + waveout = 0; + if (channel->has_iq_outputs) { + channel->iq_out[2 * (j - AGC_EXTRA)] = 0; + channel->iq_out[2 * (j - AGC_EXTRA) + 1] = 0; + } + } + } + memmove(channel->wavein, channel->wavein + WAVE_BATCH, (dev->waveend - WAVE_BATCH) * sizeof(float)); + if (channel->needs_raw_iq) { + memmove(channel->iq_in, channel->iq_in + 2 * WAVE_BATCH, (dev->waveend - WAVE_BATCH) * sizeof(float) * 2); + } #ifdef WITH_BCM_VC - afc.finalize(dev, i, fft->out); + afc.finalize(dev, i, fft->out); #else - afc.finalize(dev, i, demod_params->fftout); + afc.finalize(dev, i, demod_params->fftout); #endif /* WITH_BCM_VC */ - if (tui) { - char symbol = fparms->squelch.signal_outside_filter() ? '~' : (char)channel->axcindicate; - if(dev->mode == R_SCAN) { - GOTOXY(0, device_num * 17 + dev->row + 3); - printf("%4.0f/%3.0f%c %7.3f ", - level_to_dBFS(fparms->squelch.signal_level()), - level_to_dBFS(fparms->squelch.noise_level()), - symbol, - (dev->channels[0].freqlist[channel->freq_idx].frequency / 1000000.0)); - } else { - GOTOXY(i*10, device_num * 17 + dev->row + 3); - printf("%4.0f/%3.0f%c ", - level_to_dBFS(fparms->squelch.signal_level()), - level_to_dBFS(fparms->squelch.noise_level()), - symbol); - } - fflush(stdout); - } - - if (channel->axcindicate != NO_SIGNAL) { - channel->freqlist[channel->freq_idx].active_counter++; - } - } - if (dev->waveavail == 1) { - debug_print("devices[%d]: output channel overrun\n", device_num); - dev->output_overrun_count++; - } else { - dev->waveavail = 1; - } - dev->waveend -= WAVE_BATCH; + if (tui) { + char symbol = fparms->squelch.signal_outside_filter() ? '~' : (char)channel->axcindicate; + if (dev->mode == R_SCAN) { + GOTOXY(0, device_num * 17 + dev->row + 3); + printf("%4.0f/%3.0f%c %7.3f ", level_to_dBFS(fparms->squelch.signal_level()), level_to_dBFS(fparms->squelch.noise_level()), symbol, + (dev->channels[0].freqlist[channel->freq_idx].frequency / 1000000.0)); + } else { + GOTOXY(i * 10, device_num * 17 + dev->row + 3); + printf("%4.0f/%3.0f%c ", level_to_dBFS(fparms->squelch.signal_level()), level_to_dBFS(fparms->squelch.noise_level()), symbol); + } + fflush(stdout); + } + + if (channel->axcindicate != NO_SIGNAL) { + channel->freqlist[channel->freq_idx].active_counter++; + } + } + if (dev->waveavail == 1) { + debug_print("devices[%d]: output channel overrun\n", device_num); + dev->output_overrun_count++; + } else { + dev->waveavail = 1; + } + dev->waveend -= WAVE_BATCH; #ifdef DEBUG - gettimeofday(&te, NULL); - debug_bulk_print("waveavail %lu.%lu %lu\n", te.tv_sec, (unsigned long) te.tv_usec, (te.tv_sec - ts.tv_sec) * 1000000UL + te.tv_usec - ts.tv_usec); - ts.tv_sec = te.tv_sec; - ts.tv_usec = te.tv_usec; + gettimeofday(&te, NULL); + debug_bulk_print("waveavail %lu.%lu %lu\n", te.tv_sec, (unsigned long)te.tv_usec, (te.tv_sec - ts.tv_sec) * 1000000UL + te.tv_usec - ts.tv_usec); + ts.tv_sec = te.tv_sec; + ts.tv_usec = te.tv_usec; #endif /* DEBUG */ - demod_params->mp3_signal->send(); - dev->row++; - if (dev->row == 12) { - dev->row = 0; - } - } - - dev->input->bufs = (dev->input->bufs + bps * FFT_BATCH) % dev->input->buf_size; - device_num = next_device(demod_params, device_num); - } + demod_params->mp3_signal->send(); + dev->row++; + if (dev->row == 12) { + dev->row = 0; + } + } + + dev->input->bufs = (dev->input->bufs + bps * FFT_BATCH) % dev->input->buf_size; + device_num = next_device(demod_params, device_num); + } } void usage() { - cout<<"Usage: rtl_airband [options] [-c ]\n\ + cout << "Usage: rtl_airband [options] [-c ]\n\ \t-h\t\t\tDisplay this help text\n\ \t-f\t\t\tRun in foreground, display textual waterfalls\n\ \t-F\t\t\tRun in foreground, do not display waterfalls (for running as a systemd service)\n"; #ifdef NFM - cout<<"\t-Q\t\t\tUse quadri correlator for FM demodulation (default is atan2)\n"; + cout << "\t-Q\t\t\tUse quadri correlator for FM demodulation (default is atan2)\n"; #endif /* NFM */ #ifdef DEBUG - cout<<"\t-d \t\tLog debugging information to (default is "<\t\tLog debugging information to (default is " << DEBUG_PATH << ")\n"; #endif /* DEBUG */ - cout<<"\t-e\t\t\tPrint messages to standard error (disables syslog logging)\n"; - cout<<"\t-c \tUse non-default configuration file\n\t\t\t\t(default: "<\tUse non-default configuration file\n\t\t\t\t(default: " << CFGFILE << ")\n\ \t-v\t\t\tDisplay version and exit\n"; - exit(EXIT_SUCCESS); + exit(EXIT_SUCCESS); } static int count_devices_running() { - int ret = 0; - for(int i = 0; i < device_count; i++) { - if(devices[i].input->state == INPUT_RUNNING) { - ret++; - } - } - return ret; + int ret = 0; + for (int i = 0; i < device_count; i++) { + if (devices[i].input->state == INPUT_RUNNING) { + ret++; + } + } + return ret; } int main(int argc, char* argv[]) { #ifdef WITH_PROFILING - ProfilerStart("rtl_airband.prof"); + ProfilerStart("rtl_airband.prof"); #endif /* WITH_PROFILING */ #pragma GCC diagnostic ignored "-Wwrite-strings" - char *cfgfile = CFGFILE; - char *pidfile = PIDFILE; + char* cfgfile = CFGFILE; + char* pidfile = PIDFILE; #pragma GCC diagnostic warning "-Wwrite-strings" - int opt; - char optstring[16] = "efFhvc:"; + int opt; + char optstring[16] = "efFhvc:"; #ifdef NFM - strcat(optstring, "Q"); + strcat(optstring, "Q"); #endif /* NFM */ #ifdef DEBUG - strcat(optstring, "d:"); + strcat(optstring, "d:"); #endif /* DEBUG */ - int foreground = 0; // daemonize - int do_syslog = 1; + int foreground = 0; // daemonize + int do_syslog = 1; - while((opt = getopt(argc, argv, optstring)) != -1) { - switch(opt) { + while ((opt = getopt(argc, argv, optstring)) != -1) { + switch (opt) { #ifdef NFM - case 'Q': - fm_demod = FM_QUADRI_DEMOD; - break; + case 'Q': + fm_demod = FM_QUADRI_DEMOD; + break; #endif /* NFM */ #ifdef DEBUG - case 'd': - debug_path = strdup(optarg); - break; + case 'd': + debug_path = strdup(optarg); + break; #endif /* DEBUG */ - case 'e': - do_syslog = 0; - break; - case 'f': - foreground = 1; - tui = 1; - break; - case 'F': - foreground = 1; - tui = 0; - break; - case 'c': - cfgfile = optarg; - break; - case 'v': - cout<<"RTLSDR-Airband version "< 2*TAG_QUEUE_LEN) { - cerr<<"Configuration error: shout_metadata_delay is out of allowed range (0-"<<2 * TAG_QUEUE_LEN<<")\n"; - error(); - } - if(root.exists("localtime") && (bool)root["localtime"] == true) - use_localtime = true; - if(root.exists("multiple_demod_threads") && (bool)root["multiple_demod_threads"] == true) { + // read config + try { + Config config; + config.readFile(cfgfile); + Setting& root = config.getRoot(); + if (root.exists("pidfile")) + pidfile = strdup(root["pidfile"]); + if (root.exists("fft_size")) { + int fsize = (int)(root["fft_size"]); + fft_size_log = 0; + for (size_t i = MIN_FFT_SIZE_LOG; i <= MAX_FFT_SIZE_LOG; i++) { + if (fsize == 1 << i) { + fft_size = (size_t)fsize; + fft_size_log = i; + break; + } + } + if (fft_size_log == 0) { + cerr << "Configuration error: invalid fft_size value (must be a power of two in range " << (1 << MIN_FFT_SIZE_LOG) << "-" << (1 << MAX_FFT_SIZE_LOG) << ")\n"; + error(); + } + } + if (root.exists("shout_metadata_delay")) + shout_metadata_delay = (int)(root["shout_metadata_delay"]); + if (shout_metadata_delay < 0 || shout_metadata_delay > 2 * TAG_QUEUE_LEN) { + cerr << "Configuration error: shout_metadata_delay is out of allowed range (0-" << 2 * TAG_QUEUE_LEN << ")\n"; + error(); + } + if (root.exists("localtime") && (bool)root["localtime"] == true) + use_localtime = true; + if (root.exists("multiple_demod_threads") && (bool)root["multiple_demod_threads"] == true) { #ifdef WITH_BCM_VC - cerr<<"Using multiple_demod_threads not supported with BCM VideoCore for FFT\n"; - exit(1); + cerr << "Using multiple_demod_threads not supported with BCM VideoCore for FFT\n"; + exit(1); #endif /* WITH_BCM_VC */ - multiple_demod_threads = true; - } - if(root.exists("multiple_output_threads") && (bool)root["multiple_output_threads"] == true) { - multiple_output_threads = true; - } - if(root.exists("log_scan_activity") && (bool)root["log_scan_activity"] == true) - log_scan_activity = true; - if(root.exists("stats_filepath")) - stats_filepath = strdup(root["stats_filepath"]); + multiple_demod_threads = true; + } + if (root.exists("multiple_output_threads") && (bool)root["multiple_output_threads"] == true) { + multiple_output_threads = true; + } + if (root.exists("log_scan_activity") && (bool)root["log_scan_activity"] == true) + log_scan_activity = true; + if (root.exists("stats_filepath")) + stats_filepath = strdup(root["stats_filepath"]); #ifdef NFM - if(root.exists("tau")) - alpha = ((int)root["tau"] == 0 ? 0.0f : exp(-1.0f/(WAVE_RATE * 1e-6 * (int)root["tau"]))); + if (root.exists("tau")) + alpha = ((int)root["tau"] == 0 ? 0.0f : exp(-1.0f / (WAVE_RATE * 1e-6 * (int)root["tau"]))); #endif /* NFM */ - Setting &devs = config.lookup("devices"); - device_count = devs.getLength(); - if (device_count < 1) { - cerr<<"Configuration error: no devices defined\n"; - error(); - } - - struct sigaction sigact, pipeact; - - memset(&sigact, 0, sizeof(sigact)); - memset(&pipeact, 0, sizeof(pipeact)); - pipeact.sa_handler = SIG_IGN; - sigact.sa_handler = &sighandler; - sigaction(SIGPIPE, &pipeact, NULL); - sigaction(SIGHUP, &sigact, NULL); - sigaction(SIGINT, &sigact, NULL); - sigaction(SIGQUIT, &sigact, NULL); - sigaction(SIGTERM, &sigact, NULL); - - devices = (device_t *)XCALLOC(device_count, sizeof(device_t)); - shout_init(); - - if(do_syslog) { - openlog("rtl_airband", LOG_PID, LOG_DAEMON); - log_destination = SYSLOG; - } else if (foreground) { - log_destination = STDERR; - } else { - log_destination = NONE; - } - - if(root.exists("mixers")) { - Setting &mx = config.lookup("mixers"); - mixers = (mixer_t *)XCALLOC(mx.getLength(), sizeof(struct mixer_t)); - if((mixer_count = parse_mixers(mx)) > 0) { - mixers = (mixer_t *)XREALLOC(mixers, mixer_count * sizeof(struct mixer_t)); - } else { - free(mixers); - } - } else { - mixer_count = 0; - } - - uint32_t devs_enabled = parse_devices(devs); - if (devs_enabled < 1) { - cerr<<"Configuration error: no devices defined\n"; - error(); - } - device_count = devs_enabled; - debug_print("mixer_count=%d\n", mixer_count); + Setting& devs = config.lookup("devices"); + device_count = devs.getLength(); + if (device_count < 1) { + cerr << "Configuration error: no devices defined\n"; + error(); + } + + struct sigaction sigact, pipeact; + + memset(&sigact, 0, sizeof(sigact)); + memset(&pipeact, 0, sizeof(pipeact)); + pipeact.sa_handler = SIG_IGN; + sigact.sa_handler = &sighandler; + sigaction(SIGPIPE, &pipeact, NULL); + sigaction(SIGHUP, &sigact, NULL); + sigaction(SIGINT, &sigact, NULL); + sigaction(SIGQUIT, &sigact, NULL); + sigaction(SIGTERM, &sigact, NULL); + + devices = (device_t*)XCALLOC(device_count, sizeof(device_t)); + shout_init(); + + if (do_syslog) { + openlog("rtl_airband", LOG_PID, LOG_DAEMON); + log_destination = SYSLOG; + } else if (foreground) { + log_destination = STDERR; + } else { + log_destination = NONE; + } + + if (root.exists("mixers")) { + Setting& mx = config.lookup("mixers"); + mixers = (mixer_t*)XCALLOC(mx.getLength(), sizeof(struct mixer_t)); + if ((mixer_count = parse_mixers(mx)) > 0) { + mixers = (mixer_t*)XREALLOC(mixers, mixer_count * sizeof(struct mixer_t)); + } else { + free(mixers); + } + } else { + mixer_count = 0; + } + + uint32_t devs_enabled = parse_devices(devs); + if (devs_enabled < 1) { + cerr << "Configuration error: no devices defined\n"; + error(); + } + device_count = devs_enabled; + debug_print("mixer_count=%d\n", mixer_count); #ifdef DEBUG - for(int z = 0; z < mixer_count; z++) { - mixer_t *m = &mixers[z]; - debug_print("mixer[%d]: name=%s, input_count=%d, output_count=%d\n", z, m->name, m->input_count, m->channel.output_count); - } + for (int z = 0; z < mixer_count; z++) { + mixer_t* m = &mixers[z]; + debug_print("mixer[%d]: name=%s, input_count=%d, output_count=%d\n", z, m->name, m->input_count, m->channel.output_count); + } #endif /* DEBUG */ - } catch(const FileIOException &e) { - cerr<<"Cannot read configuration file "< 2) close(nullfd); - FILE *f = fopen(pidfile, "w"); - if(f == NULL) { - log(LOG_WARNING, "Cannot write pidfile: %s\n", strerror(errno)); - } else { - fprintf(f, "%ld\n", (long)getpid()); - fclose(f); - } - } - } - } - - for (int i = 0; i < mixer_count; i++) { - if(mixers[i].enabled == false) { - continue; // no inputs connected = no need to initialize output - } - channel_t *channel = &mixers[i].channel; - if(channel->need_mp3) { - channel->lame = airlame_init(mixers[i].channel.mode, mixers[i].channel.highpass, mixers[i].channel.lowpass); - channel->lamebuf = (unsigned char *) malloc(sizeof(unsigned char) * LAMEBUF_SIZE); - } - for (int k = 0; k < channel->output_count; k++) { - output_t *output = channel->outputs + k; - if(output->type == O_ICECAST) { - shout_setup((icecast_data *)(output->data), channel->mode); - } else if(output->type == O_UDP_STREAM) { - udp_stream_data *sdata = (udp_stream_data *)(output->data); - if (!udp_stream_init(sdata, channel->mode, (size_t)WAVE_BATCH * sizeof(float))) { - cerr << "Failed to initialize mixer " << i << " output " << k << " - aborting\n"; - error(); - } + } catch (const FileIOException& e) { + cerr << "Cannot read configuration file " << cfgfile << "\n"; + error(); + } catch (const ParseException& e) { + cerr << "Error while parsing configuration file " << cfgfile << " line " << e.getLine() << ": " << e.getError() << "\n"; + error(); + } catch (const SettingNotFoundException& e) { + cerr << "Configuration error: mandatory parameter missing: " << e.getPath() << "\n"; + error(); + } catch (const SettingTypeException& e) { + cerr << "Configuration error: invalid parameter type: " << e.getPath() << "\n"; + error(); + } catch (const ConfigException& e) { + cerr << "Unhandled config exception\n"; + error(); + } + + log(LOG_INFO, "RTLSDR-Airband version %s starting\n", RTL_AIRBAND_VERSION); + + if (!foreground) { + int pid1, pid2; + if ((pid1 = fork()) == -1) { + cerr << "Cannot fork child process: " << strerror(errno) << "\n"; + error(); + } + if (pid1) { + waitpid(-1, NULL, 0); + return (0); + } else { + if ((pid2 = fork()) == -1) { + cerr << "Cannot fork child process: " << strerror(errno) << "\n"; + error(); + } + if (pid2) { + return (0); + } else { + int nullfd, dupfd; + if ((nullfd = open("/dev/null", O_RDWR)) == -1) { + log(LOG_CRIT, "Cannot open /dev/null: %s\n", strerror(errno)); + error(); + } + for (dupfd = 0; dupfd <= 2; dupfd++) { + if (dup2(nullfd, dupfd) == -1) { + log(LOG_CRIT, "dup2(): %s\n", strerror(errno)); + error(); + } + } + if (nullfd > 2) + close(nullfd); + FILE* f = fopen(pidfile, "w"); + if (f == NULL) { + log(LOG_WARNING, "Cannot write pidfile: %s\n", strerror(errno)); + } else { + fprintf(f, "%ld\n", (long)getpid()); + fclose(f); + } + } + } + } + + for (int i = 0; i < mixer_count; i++) { + if (mixers[i].enabled == false) { + continue; // no inputs connected = no need to initialize output + } + channel_t* channel = &mixers[i].channel; + if (channel->need_mp3) { + channel->lame = airlame_init(mixers[i].channel.mode, mixers[i].channel.highpass, mixers[i].channel.lowpass); + channel->lamebuf = (unsigned char*)malloc(sizeof(unsigned char) * LAMEBUF_SIZE); + } + for (int k = 0; k < channel->output_count; k++) { + output_t* output = channel->outputs + k; + if (output->type == O_ICECAST) { + shout_setup((icecast_data*)(output->data), channel->mode); + } else if (output->type == O_UDP_STREAM) { + udp_stream_data* sdata = (udp_stream_data*)(output->data); + if (!udp_stream_init(sdata, channel->mode, (size_t)WAVE_BATCH * sizeof(float))) { + cerr << "Failed to initialize mixer " << i << " output " << k << " - aborting\n"; + error(); + } #ifdef WITH_PULSEAUDIO - } else if(output->type == O_PULSE) { - pulse_init(); - pulse_setup((pulse_data *)(output->data), channel->mode); + } else if (output->type == O_PULSE) { + pulse_init(); + pulse_setup((pulse_data*)(output->data), channel->mode); #endif /* WITH_PULSEAUDIO */ - } - } - } - for (int i = 0; i < device_count; i++) { - device_t* dev = devices + i; - for (int j = 0; j < dev->channel_count; j++) { - channel_t* channel = dev->channels + j; - - // If the channel has icecast or MP3 file output, we will attempt to - // initialize a separate LAME context for MP3 encoding. - if(channel->need_mp3) { - channel->lame = airlame_init(channel->mode, channel->highpass, channel->lowpass); - channel->lamebuf = (unsigned char *) malloc(sizeof(unsigned char) * LAMEBUF_SIZE); - } - for (int k = 0; k < channel->output_count; k++) { - output_t *output = channel->outputs + k; - if(output->type == O_ICECAST) { - shout_setup((icecast_data *)(output->data), channel->mode); - } else if(output->type == O_UDP_STREAM) { - udp_stream_data *sdata = (udp_stream_data *)(output->data); - if (!udp_stream_init(sdata, channel->mode, (size_t)WAVE_BATCH * sizeof(float))) { - cerr << "Failed to initialize device " << i << " channel " << j << " output " << k << " - aborting\n"; - error(); - } + } + } + } + for (int i = 0; i < device_count; i++) { + device_t* dev = devices + i; + for (int j = 0; j < dev->channel_count; j++) { + channel_t* channel = dev->channels + j; + + // If the channel has icecast or MP3 file output, we will attempt to + // initialize a separate LAME context for MP3 encoding. + if (channel->need_mp3) { + channel->lame = airlame_init(channel->mode, channel->highpass, channel->lowpass); + channel->lamebuf = (unsigned char*)malloc(sizeof(unsigned char) * LAMEBUF_SIZE); + } + for (int k = 0; k < channel->output_count; k++) { + output_t* output = channel->outputs + k; + if (output->type == O_ICECAST) { + shout_setup((icecast_data*)(output->data), channel->mode); + } else if (output->type == O_UDP_STREAM) { + udp_stream_data* sdata = (udp_stream_data*)(output->data); + if (!udp_stream_init(sdata, channel->mode, (size_t)WAVE_BATCH * sizeof(float))) { + cerr << "Failed to initialize device " << i << " channel " << j << " output " << k << " - aborting\n"; + error(); + } #ifdef WITH_PULSEAUDIO - } else if(output->type == O_PULSE) { - pulse_init(); - pulse_setup((pulse_data *)(output->data), channel->mode); + } else if (output->type == O_PULSE) { + pulse_init(); + pulse_setup((pulse_data*)(output->data), channel->mode); #endif /* WITH_PULSEAUDIO */ - } - } - } - if(input_init(dev->input) != 0 || dev->input->state != INPUT_INITIALIZED) { - if(errno != 0) { - cerr<<"Failed to initialize input device "<input) != 0) { - cerr<<"Failed to start input on device "<mode == R_SCAN) { -// FIXME: set errno - if(pthread_mutex_init(&dev->tag_queue_lock, NULL) != 0) { - cerr<<"Failed to initialize mutex - aborting\n"; - error(); - } -// FIXME: not needed when freq_count == 1? - pthread_create(&dev->controller_thread, NULL, &controller_thread, dev); - } - } - - int timeout = 50; // 5 seconds - while ((devices_running = count_devices_running()) != device_count && timeout > 0) { - SLEEP(100); - timeout--; - } - if((devices_running = count_devices_running()) != device_count) { - log(LOG_ERR, "%d device(s) failed to initialize - aborting\n", device_count - devices_running); - error(); - } - if (tui) { - printf("\e[1;1H\e[2J"); - - GOTOXY(0, 0); - printf(" "); - for (int i = 0; i < device_count; i++) { - GOTOXY(0, i * 17 + 1); - for (int j = 0; j < devices[i].channel_count; j++) { - printf(" %7.3f ", devices[i].channels[j].freqlist[devices[i].channels[j].freq_idx].frequency / 1000000.0); - } - if (i != device_count - 1) { - GOTOXY(0, i * 17 + 16); - printf("-------------------------------------------------------------------------------"); - } - } - } - THREAD output_check; - pthread_create(&output_check, NULL, &output_check_thread, NULL); - - int demod_thread_count = multiple_demod_threads ? device_count : 1; - demod_params_t *demod_params = (demod_params_t *)XCALLOC(demod_thread_count, sizeof(demod_params_t)); - THREAD *demod_threads = (THREAD *)XCALLOC(demod_thread_count, sizeof(THREAD)); - - int output_thread_count = 1; - if (multiple_output_threads) { - output_thread_count = demod_thread_count; - if (mixer_count > 0) { - output_thread_count++; - } - } - output_params_t *output_params = (output_params_t *)XCALLOC(output_thread_count, sizeof(output_params_t)); - THREAD *output_threads = (THREAD *)XCALLOC(output_thread_count, sizeof(THREAD)); - - // Setup the output and demod threads - if (multiple_output_threads == false) { - init_output(&output_params[0], 0, device_count, 0, mixer_count); - - if (multiple_demod_threads == false) { - init_demod(&demod_params[0], output_params[0].mp3_signal, 0, device_count); - } else { - for (int i = 0; i < demod_thread_count; i++) { - init_demod(&demod_params[i], output_params[0].mp3_signal, i, i+1); - } - } - } else { - if (multiple_demod_threads == false) { - init_output(&output_params[0], 0, device_count, 0, 0); - init_demod(&demod_params[0], output_params[0].mp3_signal, 0, device_count); - } else { - for (int i = 0; i < device_count; i++) - { - init_output(&output_params[i], i, i+1, 0, 0); - init_demod(&demod_params[i], output_params[i].mp3_signal, i, i+1); - } - } - if (mixer_count > 0) { - init_output(&output_params[output_thread_count - 1], 0, 0, 0, mixer_count); - } - } - - // Startup the output threads - for (int i = 0; i < output_thread_count; i++) - { - pthread_create(&output_threads[i], NULL, &output_thread, &output_params[i]); - } - - // Startup the mixer thread (if there is one) using the signal for the last output thread - THREAD mixer; - if(mixer_count > 0) { - pthread_create(&mixer, NULL, &mixer_thread, output_params[output_thread_count-1].mp3_signal); - } + } + } + } + if (input_init(dev->input) != 0 || dev->input->state != INPUT_INITIALIZED) { + if (errno != 0) { + cerr << "Failed to initialize input device " << i << ": " << strerror(errno) << " - aborting\n"; + } else { + cerr << "Failed to initialize input device " << i << " - aborting\n"; + } + error(); + } + if (input_start(dev->input) != 0) { + cerr << "Failed to start input on device " << i << ": " << strerror(errno) << " - aborting\n"; + error(); + } + if (dev->mode == R_SCAN) { + // FIXME: set errno + if (pthread_mutex_init(&dev->tag_queue_lock, NULL) != 0) { + cerr << "Failed to initialize mutex - aborting\n"; + error(); + } + // FIXME: not needed when freq_count == 1? + pthread_create(&dev->controller_thread, NULL, &controller_thread, dev); + } + } + + int timeout = 50; // 5 seconds + while ((devices_running = count_devices_running()) != device_count && timeout > 0) { + SLEEP(100); + timeout--; + } + if ((devices_running = count_devices_running()) != device_count) { + log(LOG_ERR, "%d device(s) failed to initialize - aborting\n", device_count - devices_running); + error(); + } + if (tui) { + printf("\e[1;1H\e[2J"); + + GOTOXY(0, 0); + printf(" "); + for (int i = 0; i < device_count; i++) { + GOTOXY(0, i * 17 + 1); + for (int j = 0; j < devices[i].channel_count; j++) { + printf(" %7.3f ", devices[i].channels[j].freqlist[devices[i].channels[j].freq_idx].frequency / 1000000.0); + } + if (i != device_count - 1) { + GOTOXY(0, i * 17 + 16); + printf("-------------------------------------------------------------------------------"); + } + } + } + THREAD output_check; + pthread_create(&output_check, NULL, &output_check_thread, NULL); + + int demod_thread_count = multiple_demod_threads ? device_count : 1; + demod_params_t* demod_params = (demod_params_t*)XCALLOC(demod_thread_count, sizeof(demod_params_t)); + THREAD* demod_threads = (THREAD*)XCALLOC(demod_thread_count, sizeof(THREAD)); + + int output_thread_count = 1; + if (multiple_output_threads) { + output_thread_count = demod_thread_count; + if (mixer_count > 0) { + output_thread_count++; + } + } + output_params_t* output_params = (output_params_t*)XCALLOC(output_thread_count, sizeof(output_params_t)); + THREAD* output_threads = (THREAD*)XCALLOC(output_thread_count, sizeof(THREAD)); + + // Setup the output and demod threads + if (multiple_output_threads == false) { + init_output(&output_params[0], 0, device_count, 0, mixer_count); + + if (multiple_demod_threads == false) { + init_demod(&demod_params[0], output_params[0].mp3_signal, 0, device_count); + } else { + for (int i = 0; i < demod_thread_count; i++) { + init_demod(&demod_params[i], output_params[0].mp3_signal, i, i + 1); + } + } + } else { + if (multiple_demod_threads == false) { + init_output(&output_params[0], 0, device_count, 0, 0); + init_demod(&demod_params[0], output_params[0].mp3_signal, 0, device_count); + } else { + for (int i = 0; i < device_count; i++) { + init_output(&output_params[i], i, i + 1, 0, 0); + init_demod(&demod_params[i], output_params[i].mp3_signal, i, i + 1); + } + } + if (mixer_count > 0) { + init_output(&output_params[output_thread_count - 1], 0, 0, 0, mixer_count); + } + } + + // Startup the output threads + for (int i = 0; i < output_thread_count; i++) { + pthread_create(&output_threads[i], NULL, &output_thread, &output_params[i]); + } + + // Startup the mixer thread (if there is one) using the signal for the last output thread + THREAD mixer; + if (mixer_count > 0) { + pthread_create(&mixer, NULL, &mixer_thread, output_params[output_thread_count - 1].mp3_signal); + } #ifdef WITH_PULSEAUDIO - pulse_start(); + pulse_start(); #endif /* WITH_PULSEAUDIO */ - sincosf_lut_init(); - - // Startup the demod threads - for (int i = 0; i < demod_thread_count; i++) { - pthread_create(&demod_threads[i], NULL, &demodulate, &demod_params[i]); - } - - // Wait for demod threads to exit - for (int i = 0; i < demod_thread_count; i++) { - pthread_join(demod_threads[i], NULL); - } - - log(LOG_INFO, "Cleaning up\n"); - for (int i = 0; i < device_count; i++) { - if(devices[i].mode == R_SCAN) - pthread_join(devices[i].controller_thread, NULL); - if(input_stop(devices[i].input) != 0 || devices[i].input->state != INPUT_STOPPED) { - if(errno != 0) { - log(LOG_ERR, "Failed do stop device #%d: %s\n", i, strerror(errno)); - } else { - log(LOG_ERR, "Failed do stop device #%d\n", i); - } - } - } - log(LOG_INFO, "Input threads closed\n"); - - for (int i = 0; i < device_count; i++) { - device_t* dev = devices + i; - disable_device_outputs(dev); - } - - if(mixer_count > 0) { - log(LOG_INFO, "Closing mixer thread\n"); - pthread_join(mixer, NULL); - } - - log(LOG_INFO, "Closing output thread(s)\n"); - for (int i = 0; i < output_thread_count; i++) { - output_params[i].mp3_signal->send(); - pthread_join(output_threads[i], NULL); - } - - for (int i = 0; i < device_count; i++) { - device_t* dev = devices + i; - for (int j = 0; j < dev->channel_count; j++) { - channel_t* channel = dev->channels + j; - if(channel->need_mp3 && channel->lame){ - lame_close(channel->lame); - } - } - } - - close_debug(); + sincosf_lut_init(); + + // Startup the demod threads + for (int i = 0; i < demod_thread_count; i++) { + pthread_create(&demod_threads[i], NULL, &demodulate, &demod_params[i]); + } + + // Wait for demod threads to exit + for (int i = 0; i < demod_thread_count; i++) { + pthread_join(demod_threads[i], NULL); + } + + log(LOG_INFO, "Cleaning up\n"); + for (int i = 0; i < device_count; i++) { + if (devices[i].mode == R_SCAN) + pthread_join(devices[i].controller_thread, NULL); + if (input_stop(devices[i].input) != 0 || devices[i].input->state != INPUT_STOPPED) { + if (errno != 0) { + log(LOG_ERR, "Failed do stop device #%d: %s\n", i, strerror(errno)); + } else { + log(LOG_ERR, "Failed do stop device #%d\n", i); + } + } + } + log(LOG_INFO, "Input threads closed\n"); + + for (int i = 0; i < device_count; i++) { + device_t* dev = devices + i; + disable_device_outputs(dev); + } + + if (mixer_count > 0) { + log(LOG_INFO, "Closing mixer thread\n"); + pthread_join(mixer, NULL); + } + + log(LOG_INFO, "Closing output thread(s)\n"); + for (int i = 0; i < output_thread_count; i++) { + output_params[i].mp3_signal->send(); + pthread_join(output_threads[i], NULL); + } + + for (int i = 0; i < device_count; i++) { + device_t* dev = devices + i; + for (int j = 0; j < dev->channel_count; j++) { + channel_t* channel = dev->channels + j; + if (channel->need_mp3 && channel->lame) { + lame_close(channel->lame); + } + } + } + + close_debug(); #ifdef WITH_PROFILING - ProfilerStop(); + ProfilerStop(); #endif /* WITH_PROFILING */ - return 0; + return 0; } // vim: ts=4 diff --git a/src/rtl_airband.h b/src/rtl_airband.h index 3f0da97..c706698 100644 --- a/src/rtl_airband.h +++ b/src/rtl_airband.h @@ -20,16 +20,16 @@ #ifndef _RTL_AIRBAND_H #define _RTL_AIRBAND_H 1 -#include -#include -#include -#include // uint32_t +#include +#include // sockaddr_in #include -#include #include -#include +#include // uint32_t +#include +#include +#include #include -#include // sockaddr_in +#include #include "config.h" @@ -44,15 +44,15 @@ #include #endif /* WITH_PULSEAUDIO */ -#include "input-common.h" // input_t -#include "squelch.h" #include "filters.h" +#include "input-common.h" // input_t #include "logging.h" +#include "squelch.h" #define ALIGNED32 __attribute__((aligned(32))) #define SLEEP(x) usleep(x * 1000) #define THREAD pthread_t -#define GOTOXY(x, y) printf("%c[%d;%df",0x1B,y,x) +#define GOTOXY(x, y) printf("%c[%d;%df", 0x1B, y, x) #ifndef SYSCONFDIR #define SYSCONFDIR "/usr/local/etc" @@ -81,319 +81,321 @@ #define DEFAULT_FFT_SIZE_LOG 9 #define MAX_FFT_SIZE_LOG 13 -#define LAMEBUF_SIZE 22000 //todo: calculate +#define LAMEBUF_SIZE 22000 // todo: calculate #define MIX_DIVISOR 2 #ifdef WITH_BCM_VC -struct sample_fft_arg -{ - size_t fft_size_by4; - GPU_FFT_COMPLEX* dest; +struct sample_fft_arg { + size_t fft_size_by4; + GPU_FFT_COMPLEX* dest; }; -extern "C" void samplefft(sample_fft_arg *a, unsigned char* buffer, float* window, float* levels); +extern "C" void samplefft(sample_fft_arg* a, unsigned char* buffer, float* window, float* levels); -# define FFT_BATCH 250 +#define FFT_BATCH 250 #else -# define FFT_BATCH 1 +#define FFT_BATCH 1 #endif /* WITH_BCM_VC */ //#define AFC_LOGGING -enum status {NO_SIGNAL = ' ', SIGNAL = '*', AFC_UP = '<', AFC_DOWN = '>' }; +enum status { NO_SIGNAL = ' ', SIGNAL = '*', AFC_UP = '<', AFC_DOWN = '>' }; enum ch_states { CH_DIRTY, CH_WORKING, CH_READY }; enum mix_modes { MM_MONO, MM_STEREO }; enum output_type { - O_ICECAST, - O_FILE, - O_RAWFILE, - O_MIXER, - O_UDP_STREAM + O_ICECAST, + O_FILE, + O_RAWFILE, + O_MIXER, + O_UDP_STREAM #ifdef WITH_PULSEAUDIO - , O_PULSE + , + O_PULSE #endif /* WITH_PULSEAUDIO */ }; struct icecast_data { - const char *hostname; - int port; + const char* hostname; + int port; #ifdef LIBSHOUT_HAS_TLS - int tls_mode; + int tls_mode; #endif /* LIBSHOUT_HAS_TLS */ - const char *username; - const char *password; - const char *mountpoint; - const char *name; - const char *genre; - const char *description; - bool send_scan_freq_tags; - shout_t *shout; + const char* username; + const char* password; + const char* mountpoint; + const char* name; + const char* genre; + const char* description; + bool send_scan_freq_tags; + shout_t* shout; }; struct file_data { - std::string basedir; - std::string basename; - std::string suffix; - std::string file_path; - std::string file_path_tmp; - bool dated_subdirectories; - bool continuous; - bool append; - bool split_on_transmission; - bool include_freq; - timeval open_time; - timeval last_write_time; - FILE *f; - enum output_type type; + std::string basedir; + std::string basename; + std::string suffix; + std::string file_path; + std::string file_path_tmp; + bool dated_subdirectories; + bool continuous; + bool append; + bool split_on_transmission; + bool include_freq; + timeval open_time; + timeval last_write_time; + FILE* f; + enum output_type type; }; struct udp_stream_data { - float *stereo_buffer; - size_t stereo_buffer_len; + float* stereo_buffer; + size_t stereo_buffer_len; - bool continuous; - const char *dest_address; - const char *dest_port; + bool continuous; + const char* dest_address; + const char* dest_port; - int send_socket; - struct sockaddr dest_sockaddr; - socklen_t dest_sockaddr_len; + int send_socket; + struct sockaddr dest_sockaddr; + socklen_t dest_sockaddr_len; }; #ifdef WITH_PULSEAUDIO struct pulse_data { - const char *server; - const char *name; - const char *sink; - const char *stream_name; - pa_context *context; - pa_stream *left, *right; - pa_channel_map lmap, rmap; - mix_modes mode; - bool continuous; + const char* server; + const char* name; + const char* sink; + const char* stream_name; + pa_context* context; + pa_stream *left, *right; + pa_channel_map lmap, rmap; + mix_modes mode; + bool continuous; }; #endif /* WITH_PULSEAUDIO */ struct mixer_data { - struct mixer_t *mixer; - int input; + struct mixer_t* mixer; + int input; }; struct output_t { - enum output_type type; - bool enabled; - bool active; - void *data; + enum output_type type; + bool enabled; + bool active; + void* data; }; struct freq_tag { - int freq; - struct timeval tv; + int freq; + struct timeval tv; }; enum modulations { - MOD_AM + MOD_AM #ifdef NFM - , MOD_NFM + , + MOD_NFM #endif /* NFM */ }; class Signal { -public: - Signal(void) { - pthread_cond_init(&cond_, NULL); - pthread_mutex_init(&mutex_, NULL); - } - void send(void) { - pthread_mutex_lock(&mutex_); - pthread_cond_signal(&cond_); - pthread_mutex_unlock(&mutex_); - } - void wait(void) { - pthread_mutex_lock(&mutex_); - pthread_cond_wait(&cond_, &mutex_); - pthread_mutex_unlock(&mutex_); - } -private: - pthread_cond_t cond_; - pthread_mutex_t mutex_; + public: + Signal(void) { + pthread_cond_init(&cond_, NULL); + pthread_mutex_init(&mutex_, NULL); + } + void send(void) { + pthread_mutex_lock(&mutex_); + pthread_cond_signal(&cond_); + pthread_mutex_unlock(&mutex_); + } + void wait(void) { + pthread_mutex_lock(&mutex_); + pthread_cond_wait(&cond_, &mutex_); + pthread_mutex_unlock(&mutex_); + } + + private: + pthread_cond_t cond_; + pthread_mutex_t mutex_; }; struct freq_t { - int frequency; // scan frequency - char *label; // frequency label - float agcavgfast; // average power, for AGC - float ampfactor; // multiplier to increase / decrease volume - Squelch squelch; - size_t active_counter; // count of loops where channel has signal - NotchFilter notch_filter; // notch filter - good to remove CTCSS tones - LowpassFilter lowpass_filter; // lowpass filter, applied to I/Q after derotation, set at bandwidth/2 to remove out of band noise - enum modulations modulation; + int frequency; // scan frequency + char* label; // frequency label + float agcavgfast; // average power, for AGC + float ampfactor; // multiplier to increase / decrease volume + Squelch squelch; + size_t active_counter; // count of loops where channel has signal + NotchFilter notch_filter; // notch filter - good to remove CTCSS tones + LowpassFilter lowpass_filter; // lowpass filter, applied to I/Q after derotation, set at bandwidth/2 to remove out of band noise + enum modulations modulation; }; struct channel_t { - float wavein[WAVE_LEN]; // FFT output waveform - float waveout[WAVE_LEN]; // waveform after squelch + AGC (left/center channel mixer output) - float waveout_r[WAVE_LEN]; // right channel mixer output - float iq_in[2*WAVE_LEN]; // raw input samples for I/Q outputs and NFM demod - float iq_out[2*WAVE_LEN]; // raw output samples for I/Q outputs (FIXME: allocate only if required) + float wavein[WAVE_LEN]; // FFT output waveform + float waveout[WAVE_LEN]; // waveform after squelch + AGC (left/center channel mixer output) + float waveout_r[WAVE_LEN]; // right channel mixer output + float iq_in[2 * WAVE_LEN]; // raw input samples for I/Q outputs and NFM demod + float iq_out[2 * WAVE_LEN]; // raw output samples for I/Q outputs (FIXME: allocate only if required) #ifdef NFM - float pr; // previous sample - real part - float pj; // previous sample - imaginary part - float prev_waveout; // previous sample - waveout before notch / ampfactor - float alpha; -#endif /* NFM */ - uint32_t dm_dphi, dm_phi; // derotation frequency and current phase value - enum mix_modes mode; // mono or stereo - status axcindicate; - unsigned char afc; //0 - AFC disabled; 1 - minimal AFC; 2 - more aggressive AFC and so on to 255 - struct freq_t *freqlist; - int freq_count; - int freq_idx; - int need_mp3; - int needs_raw_iq; - int has_iq_outputs; - enum ch_states state; // mixer channel state flag - int output_count; - output_t *outputs; - int highpass; // highpass filter cutoff - int lowpass; // lowpass filter cutoff - lame_t lame; // Context for LAME MP3 encoding if needed - unsigned char *lamebuf; // Buffer used by each lame encode + float pr; // previous sample - real part + float pj; // previous sample - imaginary part + float prev_waveout; // previous sample - waveout before notch / ampfactor + float alpha; +#endif /* NFM */ + uint32_t dm_dphi, dm_phi; // derotation frequency and current phase value + enum mix_modes mode; // mono or stereo + status axcindicate; + unsigned char afc; // 0 - AFC disabled; 1 - minimal AFC; 2 - more aggressive AFC and so on to 255 + struct freq_t* freqlist; + int freq_count; + int freq_idx; + int need_mp3; + int needs_raw_iq; + int has_iq_outputs; + enum ch_states state; // mixer channel state flag + int output_count; + output_t* outputs; + int highpass; // highpass filter cutoff + int lowpass; // lowpass filter cutoff + lame_t lame; // Context for LAME MP3 encoding if needed + unsigned char* lamebuf; // Buffer used by each lame encode }; enum rec_modes { R_MULTICHANNEL, R_SCAN }; struct device_t { - input_t *input; + input_t* input; #ifdef NFM - float alpha; + float alpha; #endif /* NFM */ - int channel_count; - size_t *base_bins, *bins; - channel_t *channels; -// FIXME: size_t - int waveend; - int waveavail; - THREAD controller_thread; - struct freq_tag tag_queue[TAG_QUEUE_LEN]; - int tq_head, tq_tail; - int last_frequency; - pthread_mutex_t tag_queue_lock; - int row; - int failed; - enum rec_modes mode; - size_t output_overrun_count; + int channel_count; + size_t *base_bins, *bins; + channel_t* channels; + // FIXME: size_t + int waveend; + int waveavail; + THREAD controller_thread; + struct freq_tag tag_queue[TAG_QUEUE_LEN]; + int tq_head, tq_tail; + int last_frequency; + pthread_mutex_t tag_queue_lock; + int row; + int failed; + enum rec_modes mode; + size_t output_overrun_count; }; struct mixinput_t { - float *wavein; - float ampfactor; - float ampl, ampr; - bool ready; - bool has_signal; - pthread_mutex_t mutex; - size_t input_overrun_count; + float* wavein; + float ampfactor; + float ampl, ampr; + bool ready; + bool has_signal; + pthread_mutex_t mutex; + size_t input_overrun_count; }; struct mixer_t { - const char *name; - bool enabled; - int interval; - size_t output_overrun_count; - int input_count; - mixinput_t *inputs; - bool *inputs_todo; - bool *input_mask; - channel_t channel; + const char* name; + bool enabled; + int interval; + size_t output_overrun_count; + int input_count; + mixinput_t* inputs; + bool* inputs_todo; + bool* input_mask; + channel_t channel; }; struct demod_params_t { - Signal *mp3_signal; - int device_start; - int device_end; + Signal* mp3_signal; + int device_start; + int device_end; #ifndef WITH_BCM_VC - fftwf_plan fft; - fftwf_complex* fftin; - fftwf_complex* fftout; + fftwf_plan fft; + fftwf_complex* fftin; + fftwf_complex* fftout; #endif /* WITH_BCM_VC */ }; struct output_params_t { - Signal *mp3_signal; - int device_start; - int device_end; - int mixer_start; - int mixer_end; + Signal* mp3_signal; + int device_start; + int device_end; + int mixer_start; + int mixer_end; }; // version.cpp -extern char const *RTL_AIRBAND_VERSION; +extern char const* RTL_AIRBAND_VERSION; // output.cpp lame_t airlame_init(mix_modes mixmode, int highpass, int lowpass); -void shout_setup(icecast_data *icecast, mix_modes mixmode); -void disable_device_outputs(device_t *dev); -void disable_channel_outputs(channel_t *channel); -void *output_check_thread(void* params); -void *output_thread(void* params); +void shout_setup(icecast_data* icecast, mix_modes mixmode); +void disable_device_outputs(device_t* dev); +void disable_channel_outputs(channel_t* channel); +void* output_check_thread(void* params); +void* output_thread(void* params); // rtl_airband.cpp extern bool use_localtime; extern bool multiple_demod_threads; extern bool multiple_output_threads; -extern char *stats_filepath; +extern char* stats_filepath; extern size_t fft_size, fft_size_log; extern int device_count, mixer_count; extern int shout_metadata_delay; extern volatile int do_exit, device_opened; extern float alpha; -extern device_t *devices; -extern mixer_t *mixers; +extern device_t* devices; +extern mixer_t* mixers; // util.cpp -int atomic_inc(volatile int *pv); -int atomic_dec(volatile int *pv); -int atomic_get(volatile int *pv); -double atofs(char *s); -double delta_sec(const timeval *start, const timeval *stop); -void log(int priority, const char *format, ...); -void tag_queue_put(device_t *dev, int freq, struct timeval tv); -void tag_queue_get(device_t *dev, struct freq_tag *tag); -void tag_queue_advance(device_t *dev); +int atomic_inc(volatile int* pv); +int atomic_dec(volatile int* pv); +int atomic_get(volatile int* pv); +double atofs(char* s); +double delta_sec(const timeval* start, const timeval* stop); +void log(int priority, const char* format, ...); +void tag_queue_put(device_t* dev, int freq, struct timeval tv); +void tag_queue_get(device_t* dev, struct freq_tag* tag); +void tag_queue_advance(device_t* dev); void sincosf_lut_init(); -void sincosf_lut(uint32_t phi, float *sine, float *cosine); -void *xcalloc(size_t nmemb, size_t size, const char *file, const int line, const char *func); -void *xrealloc(void *ptr, size_t size, const char *file, const int line, const char *func); +void sincosf_lut(uint32_t phi, float* sine, float* cosine); +void* xcalloc(size_t nmemb, size_t size, const char* file, const int line, const char* func); +void* xrealloc(void* ptr, size_t size, const char* file, const int line, const char* func); #define XCALLOC(nmemb, size) xcalloc((nmemb), (size), __FILE__, __LINE__, __func__) #define XREALLOC(ptr, size) xrealloc((ptr), (size), __FILE__, __LINE__, __func__) -float dBFS_to_level(const float &dBFS); -float level_to_dBFS(const float &level); +float dBFS_to_level(const float& dBFS); +float level_to_dBFS(const float& level); // mixer.cpp -mixer_t *getmixerbyname(const char *name); -int mixer_connect_input(mixer_t *mixer, float ampfactor, float balance); -void mixer_disable_input(mixer_t *mixer, int input_idx); -void mixer_put_samples(mixer_t *mixer, int input_idx, const float *samples, bool has_signal, unsigned int len); -void *mixer_thread(void *params); -const char *mixer_get_error(); +mixer_t* getmixerbyname(const char* name); +int mixer_connect_input(mixer_t* mixer, float ampfactor, float balance); +void mixer_disable_input(mixer_t* mixer, int input_idx); +void mixer_put_samples(mixer_t* mixer, int input_idx, const float* samples, bool has_signal, unsigned int len); +void* mixer_thread(void* params); +const char* mixer_get_error(); // config.cpp -int parse_devices(libconfig::Setting &devs); -int parse_mixers(libconfig::Setting &mx); +int parse_devices(libconfig::Setting& devs); +int parse_mixers(libconfig::Setting& mx); // udp_stream.cpp -bool udp_stream_init(udp_stream_data *sdata, mix_modes mode, size_t len); -void udp_stream_write(udp_stream_data *sdata, const float *data, size_t len); -void udp_stream_write(udp_stream_data *sdata, const float *data_left, const float *data_right, size_t len); -void udp_stream_shutdown(udp_stream_data *sdata); +bool udp_stream_init(udp_stream_data* sdata, mix_modes mode, size_t len); +void udp_stream_write(udp_stream_data* sdata, const float* data, size_t len); +void udp_stream_write(udp_stream_data* sdata, const float* data_left, const float* data_right, size_t len); +void udp_stream_shutdown(udp_stream_data* sdata); #ifdef WITH_PULSEAUDIO #define PULSE_STREAM_LATENCY_LIMIT 10000000UL // pulse.cpp void pulse_init(); -int pulse_setup(pulse_data *pdata, mix_modes mixmode); +int pulse_setup(pulse_data* pdata, mix_modes mixmode); void pulse_start(); -void pulse_shutdown(pulse_data *pdata); -void pulse_write_stream(pulse_data *pdata, mix_modes mode, const float *data_left, const float *data_right, size_t len); +void pulse_shutdown(pulse_data* pdata); +void pulse_write_stream(pulse_data* pdata, mix_modes mode, const float* data_left, const float* data_right, size_t len); #endif /* WITH_PULSEAUDIO */ #endif /* _RTL_AIRBAND_H */ diff --git a/src/squelch.cpp b/src/squelch.cpp index d81e521..459f82b 100644 --- a/src/squelch.cpp +++ b/src/squelch.cpp @@ -22,508 +22,499 @@ #ifdef DEBUG_SQUELCH #include // errno #include // strerror() -#endif /* DEBUG_SQUELCH _*/ +#endif /* DEBUG_SQUELCH _*/ -#include // pow() -#include // assert() -#include // calloc() -#include // min() +#include // calloc() +#include // min() +#include // assert() +#include // pow() -#include "logging.h" // debug_print() +#include "logging.h" // debug_print() using namespace std; -Squelch::Squelch(void) -{ - noise_floor_ = 5.0f; - set_squelch_snr_threshold(9.54f); // depends on noise_floor_, sets using_manual_level_, normal_signal_ratio_, flappy_signal_ratio_, and moving_avg_cap_ - manual_signal_level_ = -1.0; +Squelch::Squelch(void) { + noise_floor_ = 5.0f; + set_squelch_snr_threshold(9.54f); // depends on noise_floor_, sets using_manual_level_, normal_signal_ratio_, flappy_signal_ratio_, and moving_avg_cap_ + manual_signal_level_ = -1.0; - pre_filter_ = {0.001f, 0.001f}; - post_filter_ = {0.001f, 0.001f}; + pre_filter_ = {0.001f, 0.001f}; + post_filter_ = {0.001f, 0.001f}; - squelch_level_ = 0.0f; + squelch_level_ = 0.0f; - using_post_filter_ = false; - pre_vs_post_factor_ = 0.9f; + using_post_filter_ = false; + pre_vs_post_factor_ = 0.9f; - open_delay_ = 197; - close_delay_ = 197; - low_signal_abort_ = 88; + open_delay_ = 197; + close_delay_ = 197; + low_signal_abort_ = 88; - next_state_ = CLOSED; - current_state_ = CLOSED; + next_state_ = CLOSED; + current_state_ = CLOSED; - delay_ = 0; - open_count_ = 0; - sample_count_ = -1; - flappy_count_ = 0; - low_signal_count_ = 0; + delay_ = 0; + open_count_ = 0; + sample_count_ = -1; + flappy_count_ = 0; + low_signal_count_ = 0; - recent_sample_size_ = 1000; - flap_opens_threshold_ = 3; - recent_open_count_ = 0; - closed_sample_count_ = 0; + recent_sample_size_ = 1000; + flap_opens_threshold_ = 3; + recent_open_count_ = 0; + closed_sample_count_ = 0; - buffer_size_ = 102; // NOTE: this is specific to the 2nd order lowpass Bessel filter - buffer_head_ = 0; - buffer_tail_ = 1; - buffer_ = (float *)calloc(buffer_size_, sizeof(float)); + buffer_size_ = 102; // NOTE: this is specific to the 2nd order lowpass Bessel filter + buffer_head_ = 0; + buffer_tail_ = 1; + buffer_ = (float*)calloc(buffer_size_, sizeof(float)); #ifdef DEBUG_SQUELCH - debug_file_ = NULL; - raw_input_ = 0.0; - filtered_input_ = 0.0; + debug_file_ = NULL; + raw_input_ = 0.0; + filtered_input_ = 0.0; #endif /* DEBUG_SQUELCH */ - assert(open_delay_ > buffer_size_); + assert(open_delay_ > buffer_size_); - debug_print("Created Squelch, open_delay_: %d, close_delay_: %d, low_signal_abort: %d, using_manual_level_: %s\n", - open_delay_, close_delay_, low_signal_abort_, using_manual_level_ ? "true" : "false"); + debug_print("Created Squelch, open_delay_: %d, close_delay_: %d, low_signal_abort: %d, using_manual_level_: %s\n", open_delay_, close_delay_, low_signal_abort_, + using_manual_level_ ? "true" : "false"); } -void Squelch::set_squelch_level_threshold(const float &level) { - if (level > 0) { - using_manual_level_ = true; - manual_signal_level_ = level; - } else { - using_manual_level_ = false; - } +void Squelch::set_squelch_level_threshold(const float& level) { + if (level > 0) { + using_manual_level_ = true; + manual_signal_level_ = level; + } else { + using_manual_level_ = false; + } - // Need to update moving_avg_cap_ - depends on using_manual_level_ and manual_signal_level_ - calculate_moving_avg_cap(); + // Need to update moving_avg_cap_ - depends on using_manual_level_ and manual_signal_level_ + calculate_moving_avg_cap(); - debug_print("Set level threshold, using_manual_level_: %s, manual_signal_level_: %f, moving_avg_cap_: %f\n", - using_manual_level_ ? "true" : "false", manual_signal_level_, moving_avg_cap_); + debug_print("Set level threshold, using_manual_level_: %s, manual_signal_level_: %f, moving_avg_cap_: %f\n", using_manual_level_ ? "true" : "false", manual_signal_level_, moving_avg_cap_); } -void Squelch::set_squelch_snr_threshold(const float &db) { - using_manual_level_ = false; - normal_signal_ratio_ = pow(10.0, db/20.0); - flappy_signal_ratio_ = normal_signal_ratio_ * 0.9f; +void Squelch::set_squelch_snr_threshold(const float& db) { + using_manual_level_ = false; + normal_signal_ratio_ = pow(10.0, db / 20.0); + flappy_signal_ratio_ = normal_signal_ratio_ * 0.9f; - // Need to update moving_avg_cap_ - depends on using_manual_level_ and normal_signal_ratio_ - calculate_moving_avg_cap(); + // Need to update moving_avg_cap_ - depends on using_manual_level_ and normal_signal_ratio_ + calculate_moving_avg_cap(); - debug_print("SNR threshold updated, using_manual_level_: %s, normal_signal_ratio_: %f, flappy_signal_ratio_: %f, moving_avg_cap_: %f\n", - using_manual_level_ ? "true" : "false", normal_signal_ratio_, flappy_signal_ratio_, moving_avg_cap_); + debug_print("SNR threshold updated, using_manual_level_: %s, normal_signal_ratio_: %f, flappy_signal_ratio_: %f, moving_avg_cap_: %f\n", using_manual_level_ ? "true" : "false", + normal_signal_ratio_, flappy_signal_ratio_, moving_avg_cap_); } -void Squelch::set_ctcss_freq(const float &ctcss_freq, const float &sample_rate) { - // create two CTCSS detectors with different window sizes. 0.4 sec is required to tell between all the "standard" - // tones but 0.05 is enough to tell between tones ~20 Hz appart. Will use ctcss_fast_ until there are enough samples - // for ctcss_slow_ - ctcss_fast_ = CTCSS(ctcss_freq, sample_rate, sample_rate * 0.05); - ctcss_slow_ = CTCSS(ctcss_freq, sample_rate, sample_rate * 0.4); +void Squelch::set_ctcss_freq(const float& ctcss_freq, const float& sample_rate) { + // create two CTCSS detectors with different window sizes. 0.4 sec is required to tell between all the "standard" + // tones but 0.05 is enough to tell between tones ~20 Hz appart. Will use ctcss_fast_ until there are enough samples + // for ctcss_slow_ + ctcss_fast_ = CTCSS(ctcss_freq, sample_rate, sample_rate * 0.05); + ctcss_slow_ = CTCSS(ctcss_freq, sample_rate, sample_rate * 0.4); } bool Squelch::is_open(void) const { + // if current state is OPEN or CLOSING then decide based on CTCSS (if enabled) + if (current_state_ == OPEN || current_state_ == CLOSING) { + // if CTCSS is enabled then use slow (more accurate) if it has enough samples, otherwise + // use fast (will return false if also not enough samples) + if (ctcss_slow_.is_enabled()) { + if (ctcss_slow_.enough_samples()) { + return ctcss_slow_.has_tone(); + } + return ctcss_fast_.has_tone(); + } - // if current state is OPEN or CLOSING then decide based on CTCSS (if enabled) - if (current_state_ == OPEN || current_state_ == CLOSING) { + return true; + } - // if CTCSS is enabled then use slow (more accurate) if it has enough samples, otherwise - // use fast (will return false if also not enough samples) - if (ctcss_slow_.is_enabled()) { - if (ctcss_slow_.enough_samples()) { - return ctcss_slow_.has_tone(); - } - return ctcss_fast_.has_tone(); - } - - return true; - } - - return false; + return false; } bool Squelch::should_filter_sample(void) { - return ((has_pre_filter_signal() || current_state_ != CLOSED) && current_state_ != LOW_SIGNAL_ABORT); + return ((has_pre_filter_signal() || current_state_ != CLOSED) && current_state_ != LOW_SIGNAL_ABORT); } bool Squelch::should_process_audio(void) { - return ( current_state_ == OPEN || current_state_ == CLOSING ); + return (current_state_ == OPEN || current_state_ == CLOSING); } bool Squelch::first_open_sample(void) const { - return (current_state_ != OPEN && next_state_ == OPEN); + return (current_state_ != OPEN && next_state_ == OPEN); } bool Squelch::last_open_sample(void) const { - return (current_state_ == CLOSING && next_state_ == CLOSED) || - (current_state_ != LOW_SIGNAL_ABORT && next_state_ == LOW_SIGNAL_ABORT); + return (current_state_ == CLOSING && next_state_ == CLOSED) || (current_state_ != LOW_SIGNAL_ABORT && next_state_ == LOW_SIGNAL_ABORT); } bool Squelch::signal_outside_filter(void) { - return (using_post_filter_ && has_pre_filter_signal() && !has_post_filter_signal()); + return (using_post_filter_ && has_pre_filter_signal() && !has_post_filter_signal()); } -const float & Squelch::noise_level(void) const { - return noise_floor_; +const float& Squelch::noise_level(void) const { + return noise_floor_; } -const float & Squelch::signal_level(void) const { - return pre_filter_.full_; +const float& Squelch::signal_level(void) const { + return pre_filter_.full_; } -const float & Squelch::squelch_level(void) { - if (using_manual_level_) { - return manual_signal_level_; - } +const float& Squelch::squelch_level(void) { + if (using_manual_level_) { + return manual_signal_level_; + } - if (squelch_level_ == 0.0f) { - if (currently_flapping() && flappy_signal_ratio_ < normal_signal_ratio_) { - squelch_level_ = flappy_signal_ratio_ * noise_floor_; - } else { - squelch_level_ = normal_signal_ratio_ * noise_floor_; - } - } - return squelch_level_; + if (squelch_level_ == 0.0f) { + if (currently_flapping() && flappy_signal_ratio_ < normal_signal_ratio_) { + squelch_level_ = flappy_signal_ratio_ * noise_floor_; + } else { + squelch_level_ = normal_signal_ratio_ * noise_floor_; + } + } + return squelch_level_; } -const size_t & Squelch::open_count(void) const { - return open_count_; +const size_t& Squelch::open_count(void) const { + return open_count_; } -const size_t & Squelch::flappy_count(void) const { - return flappy_count_; +const size_t& Squelch::flappy_count(void) const { + return flappy_count_; } -const size_t & Squelch::ctcss_count(void) const { - return ctcss_slow_.found_count(); +const size_t& Squelch::ctcss_count(void) const { + return ctcss_slow_.found_count(); } -const size_t & Squelch::no_ctcss_count(void) const { - return ctcss_slow_.not_found_count(); +const size_t& Squelch::no_ctcss_count(void) const { + return ctcss_slow_.not_found_count(); } -void Squelch::process_raw_sample(const float &sample) { - - // Update current state based on previous state from last iteration - update_current_state(); +void Squelch::process_raw_sample(const float& sample) { + // Update current state based on previous state from last iteration + update_current_state(); #ifdef DEBUG_SQUELCH - raw_input_ = sample; + raw_input_ = sample; #endif /* DEBUG_SQUELCH */ - sample_count_++; - - // Auto noise floor - // - Doing this every 16 samples instead of every sample allows a gradual signal increase - // to cross the squelch threshold (that is a function of the noise floor) sooner. - // - Updating even when squelch is open and / or signal is outside filter means the noise - // floor (and squelch threshold) will slowly increasing during a long signal. This can lead - // to flapping, but this keeps a sudden and sustained increase of noise from locking squelch - // OPEN. - if (sample_count_ % 16 == 0) { - calculate_noise_floor(); - } - - update_moving_avg(pre_filter_, sample); - - // Apply the comparison factor before adding to the buffer, will later be used as the threshold - // for the post_filter_ - buffer_[buffer_head_] = pre_filter_.capped_ * pre_vs_post_factor_; - - // Check signal against thresholds - if (current_state_ == OPEN && !has_signal()) { - debug_print("Closing at %zu: no signal after timeout (%f, %f, %f)\n", sample_count_, pre_filter_.capped_, post_filter_.capped_, squelch_level()); - set_state(CLOSING); - } - - if (current_state_ == CLOSED && has_signal()) { - debug_print("Opening at %zu: signal (%f, %f, %f)\n", sample_count_, pre_filter_.capped_, post_filter_.capped_, squelch_level()); - set_state(OPENING); - } - - // Override squelch and close if there are repeated samples under the squelch level - // NOTE: this can cause squelch to close, but it may immediately be re-opened if the signal level still hasn't fallen after the delays - if (current_state_ != CLOSED && current_state_ != LOW_SIGNAL_ABORT) { - if (sample >= squelch_level()) { - low_signal_count_ = 0; - } else { - low_signal_count_++; - if (low_signal_count_ >= low_signal_abort_) { - debug_print("Low signal abort at %zu: low signal count %d\n", sample_count_, low_signal_count_); - set_state(LOW_SIGNAL_ABORT); - } - } - } -} - -void Squelch::process_filtered_sample(const float &sample) { + sample_count_++; + + // Auto noise floor + // - Doing this every 16 samples instead of every sample allows a gradual signal increase + // to cross the squelch threshold (that is a function of the noise floor) sooner. + // - Updating even when squelch is open and / or signal is outside filter means the noise + // floor (and squelch threshold) will slowly increasing during a long signal. This can lead + // to flapping, but this keeps a sudden and sustained increase of noise from locking squelch + // OPEN. + if (sample_count_ % 16 == 0) { + calculate_noise_floor(); + } + + update_moving_avg(pre_filter_, sample); + + // Apply the comparison factor before adding to the buffer, will later be used as the threshold + // for the post_filter_ + buffer_[buffer_head_] = pre_filter_.capped_ * pre_vs_post_factor_; + + // Check signal against thresholds + if (current_state_ == OPEN && !has_signal()) { + debug_print("Closing at %zu: no signal after timeout (%f, %f, %f)\n", sample_count_, pre_filter_.capped_, post_filter_.capped_, squelch_level()); + set_state(CLOSING); + } + + if (current_state_ == CLOSED && has_signal()) { + debug_print("Opening at %zu: signal (%f, %f, %f)\n", sample_count_, pre_filter_.capped_, post_filter_.capped_, squelch_level()); + set_state(OPENING); + } + + // Override squelch and close if there are repeated samples under the squelch level + // NOTE: this can cause squelch to close, but it may immediately be re-opened if the signal level still hasn't fallen after the delays + if (current_state_ != CLOSED && current_state_ != LOW_SIGNAL_ABORT) { + if (sample >= squelch_level()) { + low_signal_count_ = 0; + } else { + low_signal_count_++; + if (low_signal_count_ >= low_signal_abort_) { + debug_print("Low signal abort at %zu: low signal count %d\n", sample_count_, low_signal_count_); + set_state(LOW_SIGNAL_ABORT); + } + } + } +} + +void Squelch::process_filtered_sample(const float& sample) { #ifdef DEBUG_SQUELCH - filtered_input_ = sample; + filtered_input_ = sample; #endif /* DEBUG_SQUELCH */ - if (!should_filter_sample()) { - return; - } - + if (!should_filter_sample()) { + return; + } - if (current_state_ == OPENING) { - // While OPENING, need to wait until the pre-filter value gets through the buffer - if (delay_ < buffer_size_) { - return; - } - // Buffer has been filled, initialize post-filter with the pre-filter value - if (delay_ == buffer_size_) { - post_filter_ = {buffer_[buffer_tail_], buffer_[buffer_tail_]}; - } - } + if (current_state_ == OPENING) { + // While OPENING, need to wait until the pre-filter value gets through the buffer + if (delay_ < buffer_size_) { + return; + } + // Buffer has been filled, initialize post-filter with the pre-filter value + if (delay_ == buffer_size_) { + post_filter_ = {buffer_[buffer_tail_], buffer_[buffer_tail_]}; + } + } - using_post_filter_ = true; - update_moving_avg(post_filter_, sample); + using_post_filter_ = true; + update_moving_avg(post_filter_, sample); - // Always comparing the post-filter average to the buffered pre-filtered value - if (post_filter_.capped_ < buffer_[buffer_tail_]) { - debug_print("Closing at %zu: signal level post filter (%f < %f)\n", sample_count_, post_filter_.capped_, squelch_level()); - set_state(CLOSED); - } + // Always comparing the post-filter average to the buffered pre-filtered value + if (post_filter_.capped_ < buffer_[buffer_tail_]) { + debug_print("Closing at %zu: signal level post filter (%f < %f)\n", sample_count_, post_filter_.capped_, squelch_level()); + set_state(CLOSED); + } } -void Squelch::process_audio_sample(const float &sample) { +void Squelch::process_audio_sample(const float& sample) { #ifdef DEBUG_SQUELCH - audio_input_ = sample; + audio_input_ = sample; #endif /* DEBUG_SQUELCH */ - - if (!ctcss_slow_.is_enabled()) { - return; - } - - // ctcss_ is reset on transition to CLOSED and stays "unused" while CLOSED - if (current_state_ != CLOSED) { - // always send the sample to the slow (more accurate) detector, also send to the fast if there havent been enough yet - ctcss_slow_.process_audio_sample(sample); - if (!ctcss_slow_.enough_samples()) { - ctcss_fast_.process_audio_sample(sample); - } - } + + if (!ctcss_slow_.is_enabled()) { + return; + } + + // ctcss_ is reset on transition to CLOSED and stays "unused" while CLOSED + if (current_state_ != CLOSED) { + // always send the sample to the slow (more accurate) detector, also send to the fast if there havent been enough yet + ctcss_slow_.process_audio_sample(sample); + if (!ctcss_slow_.enough_samples()) { + ctcss_fast_.process_audio_sample(sample); + } + } } void Squelch::set_state(State update) { - - // Valid transitions (current_state_ -> next_state_) are: - - // - CLOSED -> CLOSED - // - CLOSED -> OPENING - // --------------------------- - // - OPENING -> CLOSED - // - OPENING -> OPENING - // - OPENING -> CLOSING - // - OPENING -> OPEN - // --------------------------- - // - CLOSING -> CLOSED - // - CLOSING -> OPENING - // - CLOSING -> CLOSING - // - CLOSING -> LOW_SIGNAL_ABORT - // - CLOSING -> OPEN - // --------------------------- - // - LOW_SIGNAL_ABORT -> CLOSED - // - LOW_SIGNAL_ABORT -> LOW_SIGNAL_ABORT - // --------------------------- - // - OPEN -> CLOSING - // - OPEN -> LOW_SIGNAL_ABORT - // - OPEN -> OPEN - - - // Invalid transistions (current_state_ -> next_state_) are: - - // CLOSED -> CLOSING (if already CLOSED cant go backwards) - if (current_state_ == CLOSED && update == CLOSING) { - update = CLOSED; - } - - // CLOSED -> LOW_SIGNAL_ABORT (if already CLOSED cant go backwards) - else if (current_state_ == CLOSED && update == LOW_SIGNAL_ABORT) { - update = CLOSED; - } - - // CLOSED -> OPEN (must go through OPENING to get to OPEN) - else if (current_state_ == CLOSED && update == OPEN) { - update = OPENING; - } - - // OPENING -> LOW_SIGNAL_ABORT (just go to CLOSED instead) - else if (current_state_ == OPENING && update == LOW_SIGNAL_ABORT) { - update = CLOSED; - } - - // LOW_SIGNAL_ABORT -> OPENING (LOW_SIGNAL_ABORT can only go to CLOSED) - // LOW_SIGNAL_ABORT -> OPEN (LOW_SIGNAL_ABORT can only go to CLOSED) - // LOW_SIGNAL_ABORT -> CLOSING (LOW_SIGNAL_ABORT can only go to CLOSED) - else if (current_state_ == LOW_SIGNAL_ABORT && update != LOW_SIGNAL_ABORT && update != CLOSED) { - update = CLOSED; - } - - // OPEN -> CLOSED (must go through CLOSING to get to CLOSED) - else if (current_state_ == OPEN && update == CLOSED) { - update = CLOSING; - } - - // OPEN -> OPENING (if already OPEN cant go backwards) - else if (current_state_ == OPEN && update == OPENING) { - update = OPEN; - } - - next_state_ = update; + // Valid transitions (current_state_ -> next_state_) are: + + // - CLOSED -> CLOSED + // - CLOSED -> OPENING + // --------------------------- + // - OPENING -> CLOSED + // - OPENING -> OPENING + // - OPENING -> CLOSING + // - OPENING -> OPEN + // --------------------------- + // - CLOSING -> CLOSED + // - CLOSING -> OPENING + // - CLOSING -> CLOSING + // - CLOSING -> LOW_SIGNAL_ABORT + // - CLOSING -> OPEN + // --------------------------- + // - LOW_SIGNAL_ABORT -> CLOSED + // - LOW_SIGNAL_ABORT -> LOW_SIGNAL_ABORT + // --------------------------- + // - OPEN -> CLOSING + // - OPEN -> LOW_SIGNAL_ABORT + // - OPEN -> OPEN + + // Invalid transistions (current_state_ -> next_state_) are: + + // CLOSED -> CLOSING (if already CLOSED cant go backwards) + if (current_state_ == CLOSED && update == CLOSING) { + update = CLOSED; + } + + // CLOSED -> LOW_SIGNAL_ABORT (if already CLOSED cant go backwards) + else if (current_state_ == CLOSED && update == LOW_SIGNAL_ABORT) { + update = CLOSED; + } + + // CLOSED -> OPEN (must go through OPENING to get to OPEN) + else if (current_state_ == CLOSED && update == OPEN) { + update = OPENING; + } + + // OPENING -> LOW_SIGNAL_ABORT (just go to CLOSED instead) + else if (current_state_ == OPENING && update == LOW_SIGNAL_ABORT) { + update = CLOSED; + } + + // LOW_SIGNAL_ABORT -> OPENING (LOW_SIGNAL_ABORT can only go to CLOSED) + // LOW_SIGNAL_ABORT -> OPEN (LOW_SIGNAL_ABORT can only go to CLOSED) + // LOW_SIGNAL_ABORT -> CLOSING (LOW_SIGNAL_ABORT can only go to CLOSED) + else if (current_state_ == LOW_SIGNAL_ABORT && update != LOW_SIGNAL_ABORT && update != CLOSED) { + update = CLOSED; + } + + // OPEN -> CLOSED (must go through CLOSING to get to CLOSED) + else if (current_state_ == OPEN && update == CLOSED) { + update = CLOSING; + } + + // OPEN -> OPENING (if already OPEN cant go backwards) + else if (current_state_ == OPEN && update == OPENING) { + update = OPEN; + } + + next_state_ = update; } void Squelch::update_current_state(void) { - if (next_state_ == OPENING) { - if (current_state_ != OPENING) { - debug_print("%zu: transitioning to OPENING\n", sample_count_); - delay_ = 0; - low_signal_count_ = 0; - using_post_filter_ = false; - current_state_ = next_state_; - } else { - // in OPENING delay - delay_++; - if (delay_ >= open_delay_) { - // After getting through OPENING delay, count this as an "open" for flap - // detection even if signal has gone. NOTE - if process_filtered_sample() would - // have already sent state to CLOSED before the delay if post_filter_.capped_ was - // too low, so that wont count towards flapping - if (closed_sample_count_ < recent_sample_size_) { - recent_open_count_++; - if (currently_flapping()) { - flappy_count_++; - } - - // Force squelch_level_ recalculation at next call to squelch_level() - squelch_level_ = 0.0f; - } - - // Check signal level after delay to either go to OPEN or CLOSED - if(has_signal()) { - next_state_ = OPEN; - } else { - debug_print("%zu: no signal after OPENING delay, going to CLOSED\n", sample_count_); - next_state_ = CLOSED; - } - } - } - } else if (next_state_ == CLOSING) { - if (current_state_ != CLOSING) { - debug_print("%zu: transitioning to CLOSING\n", sample_count_); - delay_ = 0; - current_state_ = next_state_; - } else { - // in CLOSING delay - delay_++; - if (delay_ >= close_delay_) { - if (!has_signal()) { - next_state_ = CLOSED; - } else { - debug_print("%zu: signal after CLOSING delay, reverting to OPEN\n", sample_count_); - current_state_ = OPEN; // set current_state_ to avoid incrementing open_count_ - next_state_ = OPEN; - } - } - } - } else if (next_state_ == LOW_SIGNAL_ABORT) { - if (current_state_ != LOW_SIGNAL_ABORT) { - debug_print("%zu: transitioning to LOW_SIGNAL_ABORT\n", sample_count_); - // If coming from CLOSING then keep the delay counter that has already started - if(current_state_ != CLOSING) { - delay_ = 0; - } - current_state_ = next_state_; - } else { - // in LOW_SIGNAL_ABORT delay - delay_++; - if (delay_ >= close_delay_) { - next_state_ = CLOSED; - } - } - } else if (next_state_ == OPEN && current_state_ != OPEN) { - debug_print("%zu: transitioning to OPEN\n", sample_count_); - open_count_++; - current_state_ = next_state_; - } else if (next_state_ == CLOSED && current_state_ != CLOSED) { - debug_print("%zu: transitioning to CLOSED\n", sample_count_); - using_post_filter_ = false; - closed_sample_count_ = 0; - current_state_ = next_state_; - ctcss_fast_.reset(); - ctcss_slow_.reset(); - } else if (next_state_ == CLOSED && current_state_ == CLOSED) { - // Count this as a closed sample towards flap detection (can stop counting at recent_sample_size_) - if (closed_sample_count_ < recent_sample_size_) { - closed_sample_count_++; - } else if (closed_sample_count_ == recent_sample_size_) { - recent_open_count_ = 0; - squelch_level_ = 0.0f; // Force squelch_level_ recalculation - } - } else { - current_state_ = next_state_; - } - - buffer_tail_ = (buffer_tail_ + 1 ) % buffer_size_; - buffer_head_ = (buffer_head_ + 1 ) % buffer_size_; + if (next_state_ == OPENING) { + if (current_state_ != OPENING) { + debug_print("%zu: transitioning to OPENING\n", sample_count_); + delay_ = 0; + low_signal_count_ = 0; + using_post_filter_ = false; + current_state_ = next_state_; + } else { + // in OPENING delay + delay_++; + if (delay_ >= open_delay_) { + // After getting through OPENING delay, count this as an "open" for flap + // detection even if signal has gone. NOTE - if process_filtered_sample() would + // have already sent state to CLOSED before the delay if post_filter_.capped_ was + // too low, so that wont count towards flapping + if (closed_sample_count_ < recent_sample_size_) { + recent_open_count_++; + if (currently_flapping()) { + flappy_count_++; + } + + // Force squelch_level_ recalculation at next call to squelch_level() + squelch_level_ = 0.0f; + } + + // Check signal level after delay to either go to OPEN or CLOSED + if (has_signal()) { + next_state_ = OPEN; + } else { + debug_print("%zu: no signal after OPENING delay, going to CLOSED\n", sample_count_); + next_state_ = CLOSED; + } + } + } + } else if (next_state_ == CLOSING) { + if (current_state_ != CLOSING) { + debug_print("%zu: transitioning to CLOSING\n", sample_count_); + delay_ = 0; + current_state_ = next_state_; + } else { + // in CLOSING delay + delay_++; + if (delay_ >= close_delay_) { + if (!has_signal()) { + next_state_ = CLOSED; + } else { + debug_print("%zu: signal after CLOSING delay, reverting to OPEN\n", sample_count_); + current_state_ = OPEN; // set current_state_ to avoid incrementing open_count_ + next_state_ = OPEN; + } + } + } + } else if (next_state_ == LOW_SIGNAL_ABORT) { + if (current_state_ != LOW_SIGNAL_ABORT) { + debug_print("%zu: transitioning to LOW_SIGNAL_ABORT\n", sample_count_); + // If coming from CLOSING then keep the delay counter that has already started + if (current_state_ != CLOSING) { + delay_ = 0; + } + current_state_ = next_state_; + } else { + // in LOW_SIGNAL_ABORT delay + delay_++; + if (delay_ >= close_delay_) { + next_state_ = CLOSED; + } + } + } else if (next_state_ == OPEN && current_state_ != OPEN) { + debug_print("%zu: transitioning to OPEN\n", sample_count_); + open_count_++; + current_state_ = next_state_; + } else if (next_state_ == CLOSED && current_state_ != CLOSED) { + debug_print("%zu: transitioning to CLOSED\n", sample_count_); + using_post_filter_ = false; + closed_sample_count_ = 0; + current_state_ = next_state_; + ctcss_fast_.reset(); + ctcss_slow_.reset(); + } else if (next_state_ == CLOSED && current_state_ == CLOSED) { + // Count this as a closed sample towards flap detection (can stop counting at recent_sample_size_) + if (closed_sample_count_ < recent_sample_size_) { + closed_sample_count_++; + } else if (closed_sample_count_ == recent_sample_size_) { + recent_open_count_ = 0; + squelch_level_ = 0.0f; // Force squelch_level_ recalculation + } + } else { + current_state_ = next_state_; + } + + buffer_tail_ = (buffer_tail_ + 1) % buffer_size_; + buffer_head_ = (buffer_head_ + 1) % buffer_size_; #ifdef DEBUG_SQUELCH - debug_state(); + debug_state(); #endif /* DEBUG_SQUELCH */ } bool Squelch::has_pre_filter_signal(void) { - return pre_filter_.capped_ >= squelch_level(); + return pre_filter_.capped_ >= squelch_level(); } bool Squelch::has_post_filter_signal(void) { - return using_post_filter_ && post_filter_.capped_ >= buffer_[buffer_tail_]; + return using_post_filter_ && post_filter_.capped_ >= buffer_[buffer_tail_]; } bool Squelch::has_signal(void) { - if (using_post_filter_) { - return has_pre_filter_signal() && has_post_filter_signal(); - } - return has_pre_filter_signal(); + if (using_post_filter_) { + return has_pre_filter_signal() && has_post_filter_signal(); + } + return has_pre_filter_signal(); } void Squelch::calculate_noise_floor(void) { - static const float decay_factor = 0.97f; - static const float new_factor = 1.0 - decay_factor; + static const float decay_factor = 0.97f; + static const float new_factor = 1.0 - decay_factor; - noise_floor_ = noise_floor_ * decay_factor + std::min(pre_filter_.capped_, noise_floor_) * new_factor + 1e-6f; + noise_floor_ = noise_floor_ * decay_factor + std::min(pre_filter_.capped_, noise_floor_) * new_factor + 1e-6f; - debug_print("%zu: noise floor is now %f\n", sample_count_, noise_floor_); + debug_print("%zu: noise floor is now %f\n", sample_count_, noise_floor_); - // Need to update moving_avg_cap_ - depends on noise_floor_ - calculate_moving_avg_cap(); + // Need to update moving_avg_cap_ - depends on noise_floor_ + calculate_moving_avg_cap(); - // Force squelch_level_ recalculation at next call to squelch_level() - depends on noise_floor_ - squelch_level_ = 0.0f; + // Force squelch_level_ recalculation at next call to squelch_level() - depends on noise_floor_ + squelch_level_ = 0.0f; } void Squelch::calculate_moving_avg_cap(void) { - // set max value for MovingAverage's capped_ to 1.5 x the normal / manual squelch level. - if (using_manual_level_) { - moving_avg_cap_ = 1.5f * manual_signal_level_; - } else { - moving_avg_cap_ = 1.5f * normal_signal_ratio_ * noise_floor_; - } + // set max value for MovingAverage's capped_ to 1.5 x the normal / manual squelch level. + if (using_manual_level_) { + moving_avg_cap_ = 1.5f * manual_signal_level_; + } else { + moving_avg_cap_ = 1.5f * normal_signal_ratio_ * noise_floor_; + } } -void Squelch::update_moving_avg(MovingAverage &avg, const float &sample) { - static const float decay_factor = 0.99f; - static const float new_factor = 1.0 - decay_factor; +void Squelch::update_moving_avg(MovingAverage& avg, const float& sample) { + static const float decay_factor = 0.99f; + static const float new_factor = 1.0 - decay_factor; - avg.full_ = avg.full_ * decay_factor + sample * new_factor; + avg.full_ = avg.full_ * decay_factor + sample * new_factor; - // Cap average level, this lets the average drop after the signal goes away more quickly - // (if current value and update are both at/above the max then can avoid the float multiplications) - if (avg.capped_ >= moving_avg_cap_ && sample >= moving_avg_cap_) { - avg.capped_ = moving_avg_cap_; - } else { - avg.capped_ = min(moving_avg_cap_, avg.capped_ * decay_factor + sample * new_factor); - } + // Cap average level, this lets the average drop after the signal goes away more quickly + // (if current value and update are both at/above the max then can avoid the float multiplications) + if (avg.capped_ >= moving_avg_cap_ && sample >= moving_avg_cap_) { + avg.capped_ = moving_avg_cap_; + } else { + avg.capped_ = min(moving_avg_cap_, avg.capped_ * decay_factor + sample * new_factor); + } } bool Squelch::currently_flapping(void) const { - return recent_open_count_ >= flap_opens_threshold_; + return recent_open_count_ >= flap_opens_threshold_; } #ifdef DEBUG_SQUELCH @@ -532,113 +523,113 @@ bool Squelch::currently_flapping(void) const { ================== Values written to file are: - - (int16_t) process_raw_sample input - - (int16_t) process_filtered_sample input - - (int16_t) process_audio_sample input - - (int16_t) noise_floor_ - - (int16_t) pre_filter_.capped_ - - (int16_t) post_filter_.capped_ - - (int) current_state_ - - (int) delay_ - - (int) low_signalcount_ - - (int) ctcss_fast_.has_tone() - - (int) ctcss_slow_.has_tone() + - (int16_t) process_raw_sample input + - (int16_t) process_filtered_sample input + - (int16_t) process_audio_sample input + - (int16_t) noise_floor_ + - (int16_t) pre_filter_.capped_ + - (int16_t) post_filter_.capped_ + - (int) current_state_ + - (int) delay_ + - (int) low_signalcount_ + - (int) ctcss_fast_.has_tone() + - (int) ctcss_slow_.has_tone() The output file can be read / plotted in python as follows: - import matplotlib.pyplot as plt - import numpy as np - - def plot_squelch_debug(filepath): - - dt = np.dtype([('raw_input', np.single), - ('filtered_input', np.single), - ('audio_input', np.single), - ('noise_floor', np.single), - ('pre_filter_capped', np.single), - ('post_filter_capped', np.single), - ('current_state', np.intc), - ('delay', np.intc), - ('low_signalcount', np.intc), - ('ctcss_fast_has_tone', np.intc), - ('ctcss_slow_has_tone', np.intc) - ]) - - dat = np.fromfile(filepath, dtype=dt) - - plt.figure() - plt.plot(dat['raw_input'], 'b') - plt.plot(dat['pre_filter_capped'], 'g') - plt.plot(dat['noise_floor'], 'r') - plt.show(block=False) - - plt.figure() - plt.plot(dat['post_filter_capped'], 'k') - plt.show(block=False) - - plt.figure() - axis = plt.subplot2grid((3, 1), (0, 0)) - axis.plot(dat['current_state'], 'c') - axis = plt.subplot2grid((3, 1), (1, 0)) - axis.plot(dat['delay'], 'm') - axis = plt.subplot2grid((3, 1), (2, 0)) - axis.plot(dat['low_signalcount'], 'y') - plt.show(block=False) - - return + import matplotlib.pyplot as plt + import numpy as np + + def plot_squelch_debug(filepath): + + dt = np.dtype([('raw_input', np.single), + ('filtered_input', np.single), + ('audio_input', np.single), + ('noise_floor', np.single), + ('pre_filter_capped', np.single), + ('post_filter_capped', np.single), + ('current_state', np.intc), + ('delay', np.intc), + ('low_signalcount', np.intc), + ('ctcss_fast_has_tone', np.intc), + ('ctcss_slow_has_tone', np.intc) + ]) + + dat = np.fromfile(filepath, dtype=dt) + + plt.figure() + plt.plot(dat['raw_input'], 'b') + plt.plot(dat['pre_filter_capped'], 'g') + plt.plot(dat['noise_floor'], 'r') + plt.show(block=False) + + plt.figure() + plt.plot(dat['post_filter_capped'], 'k') + plt.show(block=False) + + plt.figure() + axis = plt.subplot2grid((3, 1), (0, 0)) + axis.plot(dat['current_state'], 'c') + axis = plt.subplot2grid((3, 1), (1, 0)) + axis.plot(dat['delay'], 'm') + axis = plt.subplot2grid((3, 1), (2, 0)) + axis.plot(dat['low_signalcount'], 'y') + plt.show(block=False) + + return */ Squelch::~Squelch(void) { - if (debug_file_) { - fclose(debug_file_); - } + if (debug_file_) { + fclose(debug_file_); + } } -void Squelch::set_debug_file(const char *filepath) { - debug_file_ = fopen(filepath, "wb"); +void Squelch::set_debug_file(const char* filepath) { + debug_file_ = fopen(filepath, "wb"); } -void Squelch::debug_value(const float &value) { - if (!debug_file_) { - return; - } +void Squelch::debug_value(const float& value) { + if (!debug_file_) { + return; + } - if (fwrite(&value, sizeof(value), 1, debug_file_) != 1) { - debug_print("Error writing to squelch debug file: %s\n", strerror(errno)); - } + if (fwrite(&value, sizeof(value), 1, debug_file_) != 1) { + debug_print("Error writing to squelch debug file: %s\n", strerror(errno)); + } } -void Squelch::debug_value(const int &value) { - if (!debug_file_) { - return; - } +void Squelch::debug_value(const int& value) { + if (!debug_file_) { + return; + } - if (fwrite(&value, sizeof(value), 1, debug_file_) != 1) { - debug_print("Error writing to squelch debug file: %s\n", strerror(errno)); - } + if (fwrite(&value, sizeof(value), 1, debug_file_) != 1) { + debug_print("Error writing to squelch debug file: %s\n", strerror(errno)); + } } void Squelch::debug_state(void) { - if (!debug_file_) { - return; - } - debug_value(raw_input_); - debug_value(filtered_input_); - debug_value(audio_input_); - - raw_input_ = 0.0; - filtered_input_ = 0.0; - audio_input_ = 0.0; - - debug_value(noise_floor_); - debug_value(pre_filter_.capped_); - debug_value(post_filter_.capped_); - debug_value((int)current_state_); - debug_value(delay_); - debug_value(low_signal_count_); - debug_value((int)ctcss_fast_.has_tone()); - debug_value((int)ctcss_slow_.has_tone()); + if (!debug_file_) { + return; + } + debug_value(raw_input_); + debug_value(filtered_input_); + debug_value(audio_input_); + + raw_input_ = 0.0; + filtered_input_ = 0.0; + audio_input_ = 0.0; + + debug_value(noise_floor_); + debug_value(pre_filter_.capped_); + debug_value(post_filter_.capped_); + debug_value((int)current_state_); + debug_value(delay_); + debug_value(low_signal_count_); + debug_value((int)ctcss_fast_.has_tone()); + debug_value((int)ctcss_slow_.has_tone()); } #endif /* DEBUG_SQUELCH */ diff --git a/src/squelch.h b/src/squelch.h index 3ba539e..51b7290 100644 --- a/src/squelch.h +++ b/src/squelch.h @@ -24,7 +24,7 @@ #ifdef DEBUG_SQUELCH #include // needed for debug file output -#endif /* DEBUG_SQUELCH */ +#endif /* DEBUG_SQUELCH */ #include "ctcss.h" @@ -63,118 +63,118 @@ CTCSS tone detection can be enabled. If used, two tone detectors are created at different window lengths. The “fast” detector has less resolution but needs fewer samples while the “slow” detector is more accurate. When CTCSS is enabled, squelch remains CLOSED for an additional 0.05 sec until a tone is detected by the “fast” - detector. + detector. */ class Squelch { -public: - Squelch(); + public: + Squelch(); - void set_squelch_level_threshold(const float &level); - void set_squelch_snr_threshold(const float &db); - void set_ctcss_freq(const float &ctcss_freq, const float &sample_rate); + void set_squelch_level_threshold(const float& level); + void set_squelch_snr_threshold(const float& db); + void set_ctcss_freq(const float& ctcss_freq, const float& sample_rate); - void process_raw_sample(const float &sample); - void process_filtered_sample(const float &sample); - void process_audio_sample(const float &sample); + void process_raw_sample(const float& sample); + void process_filtered_sample(const float& sample); + void process_audio_sample(const float& sample); - bool is_open(void) const; - bool should_filter_sample(void); - bool should_process_audio(void); + bool is_open(void) const; + bool should_filter_sample(void); + bool should_process_audio(void); - bool first_open_sample(void) const; - bool last_open_sample(void) const; - bool signal_outside_filter(void); + bool first_open_sample(void) const; + bool last_open_sample(void) const; + bool signal_outside_filter(void); - const float & noise_level(void) const; - const float & signal_level(void) const; - const float & squelch_level(void); + const float& noise_level(void) const; + const float& signal_level(void) const; + const float& squelch_level(void); - const size_t & open_count(void) const; - const size_t & flappy_count(void) const; - const size_t & ctcss_count(void) const; - const size_t & no_ctcss_count(void) const; + const size_t& open_count(void) const; + const size_t& flappy_count(void) const; + const size_t& ctcss_count(void) const; + const size_t& no_ctcss_count(void) const; #ifdef DEBUG_SQUELCH - ~Squelch(void); - void set_debug_file(const char *filepath); + ~Squelch(void); + void set_debug_file(const char* filepath); #endif /* DEBUG_SQUELCH */ -private: - enum State { - CLOSED, // Audio is suppressed - OPENING, // Transitioning closed -> open - CLOSING, // Transitioning open -> closed - LOW_SIGNAL_ABORT, // Like CLOSING but is_open() is false - OPEN // Audio not suppressed - }; - - struct MovingAverage { - float full_; - float capped_; - }; - - float noise_floor_; // noise level - bool using_manual_level_; // if using a manually set signal level threshold - float manual_signal_level_; // manually configured squelch level, < 0 for disabled - float normal_signal_ratio_; // signal-to-noise ratio for normal squelch - ratio, not in dB - float flappy_signal_ratio_; // signal-to-noise ratio for flappy squelch - ratio, not in dB - - float moving_avg_cap_; // the max value for capped moving average - MovingAverage pre_filter_; // average signal level for reference sample - MovingAverage post_filter_; // average signal level for post-filter sample - - float squelch_level_; // cached calculation of the squelch_level() value - - bool using_post_filter_; // if the caller is providing filtered samples - float pre_vs_post_factor_; // multiplier when doing pre vs post filter compaison - - int open_delay_; // how long to wait after signal level crosses squelch to open - int close_delay_; // how long to wait after signal level crosses squelch to close - int low_signal_abort_; // number of repeated samples below squelch to cause a close - - State next_state_; - State current_state_; - - int delay_; // samples to wait before making next squelch decision - size_t open_count_; // number of times squelch is opened - size_t sample_count_; // number of samples processed (for logging) - size_t flappy_count_; // number of times squelch was detected as flapping OPEN/CLOSED - int low_signal_count_; // number of repeated samples below squelch - - // Flap detection parameters - size_t recent_sample_size_; // number of samples defined as "recent" - size_t flap_opens_threshold_; // number of opens to count as flapping - size_t recent_open_count_; // number of times squelch recently opened - size_t closed_sample_count_; // number of continuous samples where squelch has been CLOSED - - // Buffered pre-filtered values - int buffer_size_; // size of buffer - int buffer_head_; // index to add new values - int buffer_tail_; // index to read buffered values - float *buffer_; // buffer - - CTCSS ctcss_fast_; // ctcss tone detection - CTCSS ctcss_slow_; // ctcss tone detection - - void set_state(State update); - void update_current_state(void); - bool has_pre_filter_signal(void); - bool has_post_filter_signal(void); - bool has_signal(void); - void calculate_noise_floor(void); - void calculate_moving_avg_cap(void); - void update_moving_avg(MovingAverage &avg, const float &sample); - bool currently_flapping(void) const; + private: + enum State { + CLOSED, // Audio is suppressed + OPENING, // Transitioning closed -> open + CLOSING, // Transitioning open -> closed + LOW_SIGNAL_ABORT, // Like CLOSING but is_open() is false + OPEN // Audio not suppressed + }; + + struct MovingAverage { + float full_; + float capped_; + }; + + float noise_floor_; // noise level + bool using_manual_level_; // if using a manually set signal level threshold + float manual_signal_level_; // manually configured squelch level, < 0 for disabled + float normal_signal_ratio_; // signal-to-noise ratio for normal squelch - ratio, not in dB + float flappy_signal_ratio_; // signal-to-noise ratio for flappy squelch - ratio, not in dB + + float moving_avg_cap_; // the max value for capped moving average + MovingAverage pre_filter_; // average signal level for reference sample + MovingAverage post_filter_; // average signal level for post-filter sample + + float squelch_level_; // cached calculation of the squelch_level() value + + bool using_post_filter_; // if the caller is providing filtered samples + float pre_vs_post_factor_; // multiplier when doing pre vs post filter compaison + + int open_delay_; // how long to wait after signal level crosses squelch to open + int close_delay_; // how long to wait after signal level crosses squelch to close + int low_signal_abort_; // number of repeated samples below squelch to cause a close + + State next_state_; + State current_state_; + + int delay_; // samples to wait before making next squelch decision + size_t open_count_; // number of times squelch is opened + size_t sample_count_; // number of samples processed (for logging) + size_t flappy_count_; // number of times squelch was detected as flapping OPEN/CLOSED + int low_signal_count_; // number of repeated samples below squelch + + // Flap detection parameters + size_t recent_sample_size_; // number of samples defined as "recent" + size_t flap_opens_threshold_; // number of opens to count as flapping + size_t recent_open_count_; // number of times squelch recently opened + size_t closed_sample_count_; // number of continuous samples where squelch has been CLOSED + + // Buffered pre-filtered values + int buffer_size_; // size of buffer + int buffer_head_; // index to add new values + int buffer_tail_; // index to read buffered values + float* buffer_; // buffer + + CTCSS ctcss_fast_; // ctcss tone detection + CTCSS ctcss_slow_; // ctcss tone detection + + void set_state(State update); + void update_current_state(void); + bool has_pre_filter_signal(void); + bool has_post_filter_signal(void); + bool has_signal(void); + void calculate_noise_floor(void); + void calculate_moving_avg_cap(void); + void update_moving_avg(MovingAverage& avg, const float& sample); + bool currently_flapping(void) const; #ifdef DEBUG_SQUELCH - FILE *debug_file_; - float raw_input_; - float filtered_input_; - float audio_input_; - void debug_value(const float &value); - void debug_value(const int &value); - void debug_state(void); + FILE* debug_file_; + float raw_input_; + float filtered_input_; + float audio_input_; + void debug_value(const float& value); + void debug_value(const int& value); + void debug_state(void); #endif /* DEBUG_SQUELCH */ }; diff --git a/src/test_base_class.cpp b/src/test_base_class.cpp index 1bc2841..8931e8d 100644 --- a/src/test_base_class.cpp +++ b/src/test_base_class.cpp @@ -25,117 +25,114 @@ using namespace std; -void delete_directory(const string &root) { - DIR *dp = NULL; - - dp = opendir(root.c_str()); - if (dp == NULL) { - cerr << "Error opening directory " << root << endl; - return; - } - - string current_dir = "."; - string parent_dir = ".."; - - struct dirent *entry = NULL; - while ((entry = readdir(dp))) { - - - if (current_dir.compare(entry->d_name) == 0 || parent_dir.compare(entry->d_name) == 0) { - continue; - } - - struct stat info; - string filepath = root + "/" + string(entry->d_name); - - if (stat(filepath.c_str(), &info) != 0) { - cerr << "Error getting info on " << filepath.c_str() << ": " << strerror(errno) << endl; - continue; - } - - if (S_ISDIR(info.st_mode)) { - delete_directory (filepath); - } else { - unlink(filepath.c_str()); - } - } - - closedir(dp); - rmdir(root.c_str()); +void delete_directory(const string& root) { + DIR* dp = NULL; + + dp = opendir(root.c_str()); + if (dp == NULL) { + cerr << "Error opening directory " << root << endl; + return; + } + + string current_dir = "."; + string parent_dir = ".."; + + struct dirent* entry = NULL; + while ((entry = readdir(dp))) { + if (current_dir.compare(entry->d_name) == 0 || parent_dir.compare(entry->d_name) == 0) { + continue; + } + + struct stat info; + string filepath = root + "/" + string(entry->d_name); + + if (stat(filepath.c_str(), &info) != 0) { + cerr << "Error getting info on " << filepath.c_str() << ": " << strerror(errno) << endl; + continue; + } + + if (S_ISDIR(info.st_mode)) { + delete_directory(filepath); + } else { + unlink(filepath.c_str()); + } + } + + closedir(dp); + rmdir(root.c_str()); } string make_temp_dir(void) { - char temp_path_template[] = "/tmp/temp_unittest_dir_XXXXXX"; - if (mkdtemp(temp_path_template) == NULL) { - cerr << "Error making temp dir for test files: " << strerror(errno) << endl; - return ""; - } - return string(temp_path_template); + char temp_path_template[] = "/tmp/temp_unittest_dir_XXXXXX"; + if (mkdtemp(temp_path_template) == NULL) { + cerr << "Error making temp dir for test files: " << strerror(errno) << endl; + return ""; + } + return string(temp_path_template); } void TestBaseClass::SetUp(void) { - ::testing::Test::SetUp(); + ::testing::Test::SetUp(); - // setup debug log file for each test - temp_dir = make_temp_dir(); - ASSERT_FALSE(temp_dir.empty()); - string debug_filepath = temp_dir + "/debug_file.log"; - init_debug(debug_filepath.c_str()); + // setup debug log file for each test + temp_dir = make_temp_dir(); + ASSERT_FALSE(temp_dir.empty()); + string debug_filepath = temp_dir + "/debug_file.log"; + init_debug(debug_filepath.c_str()); - // point logging to stderr - log_destination = STDERR; + // point logging to stderr + log_destination = STDERR; } -void TestBaseClass::TearDown(void) -{ - ::testing::Test::TearDown(); - close_debug(); - delete_directory(temp_dir); +void TestBaseClass::TearDown(void) { + ::testing::Test::TearDown(); + close_debug(); + delete_directory(temp_dir); } TEST(TestHelpers, make_temp_dir) { - // make a temp dir - string temp_dir = make_temp_dir(); + // make a temp dir + string temp_dir = make_temp_dir(); - // path should not be empty string - ASSERT_FALSE(temp_dir.empty()); + // path should not be empty string + ASSERT_FALSE(temp_dir.empty()); - // a directory should exist at the path - struct stat info; - ASSERT_EQ(stat(temp_dir.c_str(), &info), 0); - EXPECT_TRUE(S_ISDIR(info.st_mode)); + // a directory should exist at the path + struct stat info; + ASSERT_EQ(stat(temp_dir.c_str(), &info), 0); + EXPECT_TRUE(S_ISDIR(info.st_mode)); - delete_directory(temp_dir); + delete_directory(temp_dir); } TEST(TestHelpers, delete_directory) { - // make a temp dir - string temp_dir = make_temp_dir(); - ASSERT_FALSE(temp_dir.empty()); - - // build a bunch of nested sub-dirs and files - string path = temp_dir; - for (int i = 0 ; i < 5; ++i) { - path = path + "/sub_dir"; - mkdir(path.c_str(), 0777); - - string filename = path + "/some_file"; - fclose(fopen(filename.c_str(), "w")); - } - - // last sub-dir should exist and be a directory - struct stat info; - ASSERT_EQ(stat(path.c_str(), &info), 0); - EXPECT_TRUE(S_ISDIR(info.st_mode)); - - // last sub-dir should have a file in it - string filename = path + "/some_file"; - ASSERT_EQ(stat(filename.c_str(), &info), 0); - EXPECT_TRUE(S_ISREG(info.st_mode)); - - // delete the root temp dir - delete_directory(temp_dir); - - // root temp dir should no longer exist - ASSERT_NE(stat(temp_dir.c_str(), &info), 0); + // make a temp dir + string temp_dir = make_temp_dir(); + ASSERT_FALSE(temp_dir.empty()); + + // build a bunch of nested sub-dirs and files + string path = temp_dir; + for (int i = 0; i < 5; ++i) { + path = path + "/sub_dir"; + mkdir(path.c_str(), 0777); + + string filename = path + "/some_file"; + fclose(fopen(filename.c_str(), "w")); + } + + // last sub-dir should exist and be a directory + struct stat info; + ASSERT_EQ(stat(path.c_str(), &info), 0); + EXPECT_TRUE(S_ISDIR(info.st_mode)); + + // last sub-dir should have a file in it + string filename = path + "/some_file"; + ASSERT_EQ(stat(filename.c_str(), &info), 0); + EXPECT_TRUE(S_ISREG(info.st_mode)); + + // delete the root temp dir + delete_directory(temp_dir); + + // root temp dir should no longer exist + ASSERT_NE(stat(temp_dir.c_str(), &info), 0); } \ No newline at end of file diff --git a/src/test_base_class.h b/src/test_base_class.h index c737d12..f5c07a3 100644 --- a/src/test_base_class.h +++ b/src/test_base_class.h @@ -24,13 +24,12 @@ #include -class TestBaseClass : public ::testing::Test -{ -protected: - void SetUp(void); - void TearDown(void); +class TestBaseClass : public ::testing::Test { + protected: + void SetUp(void); + void TearDown(void); - std::string temp_dir; + std::string temp_dir; }; #endif /* _TEST_BASE_CLASS_H */ diff --git a/src/test_ctcss.cpp b/src/test_ctcss.cpp index 8660462..e010c93 100644 --- a/src/test_ctcss.cpp +++ b/src/test_ctcss.cpp @@ -17,146 +17,139 @@ * along with this program. If not, see . */ -#include "test_base_class.h" #include "generate_signal.h" +#include "test_base_class.h" #include "ctcss.h" using namespace std; class CTCSSTest : public TestBaseClass { -protected: - int sample_rate; - int fast_window_size; - int slow_window_size; - - void SetUp(void) { - TestBaseClass::SetUp(); - sample_rate = 8000; - fast_window_size = sample_rate * 0.05; - slow_window_size = sample_rate * 0.4; - } - - void write_file(const vector &samples, const string &filepath) { - - cerr << "writing file out to " << filepath << endl; - - FILE* fp = fopen(filepath.c_str(), "wb"); - - for (auto sample : samples) { - fwrite(&sample, sizeof(float), 1, fp); - } - fclose(fp); - } - - void load_from_file(CTCSS &ctcss, const string &filepath) { - - FILE* fp = fopen(filepath.c_str(), "rb"); - - while (!ctcss.enough_samples()) { - float sample; - if(fread(&sample, sizeof(float), 1, fp) != 1) { - break; - } - ctcss.process_audio_sample(sample); - } - fclose(fp); - - ASSERT_TRUE(ctcss.enough_samples()); - } - - void test_all_tones(GenerateSignal &signal, const float &tone = 0) { - for (auto standard_tone : CTCSS::standard_tones) { - // skipping tones within +/- 5Hz - if (abs(standard_tone - tone) < 5) { - continue; - } - - CTCSS ctcss(standard_tone, sample_rate, slow_window_size); - vector samples; - run_signal(ctcss, signal, samples); - - EXPECT_FALSE(ctcss.has_tone()) << "Tone of " << standard_tone << " found, expected " << tone; - - // on failure write out a file for debugging - if (ctcss.has_tone()) { - // double the samples to write to the file for later testing - size_t initial_count = samples.size(); - while (samples.size() < initial_count * 2) { - samples.push_back(signal.get_sample()); - } - - string filepath = "/tmp/found_" + to_string(standard_tone) + "_expected_" + to_string(tone); - write_file(samples, filepath); - } - } - if (tone != 0) { - CTCSS ctcss(tone, sample_rate, slow_window_size); - vector samples; - run_signal(ctcss, signal, samples); - - EXPECT_TRUE(ctcss.has_tone()) << "Expected tone of " << tone << " not found"; - - // on failure write out a file for debugging - if (!ctcss.has_tone()) { - // double the samples to write to the file for later testing - size_t initial_count = samples.size(); - while (samples.size() < initial_count * 2) { - samples.push_back(signal.get_sample()); - } - - string filepath = "/tmp/didnt_find_" + to_string(tone); - write_file(samples, filepath); - } - } - } - - void run_signal(CTCSS &ctcss, GenerateSignal &signal, vector &samples) { - EXPECT_TRUE(ctcss.is_enabled()) << "CTCSS not enabled"; - while( !ctcss.enough_samples() ) { - float sample = signal.get_sample(); - samples.push_back(sample); - ctcss.process_audio_sample(sample); - } - } + protected: + int sample_rate; + int fast_window_size; + int slow_window_size; + + void SetUp(void) { + TestBaseClass::SetUp(); + sample_rate = 8000; + fast_window_size = sample_rate * 0.05; + slow_window_size = sample_rate * 0.4; + } + + void write_file(const vector& samples, const string& filepath) { + cerr << "writing file out to " << filepath << endl; + + FILE* fp = fopen(filepath.c_str(), "wb"); + + for (auto sample : samples) { + fwrite(&sample, sizeof(float), 1, fp); + } + fclose(fp); + } + + void load_from_file(CTCSS& ctcss, const string& filepath) { + FILE* fp = fopen(filepath.c_str(), "rb"); + + while (!ctcss.enough_samples()) { + float sample; + if (fread(&sample, sizeof(float), 1, fp) != 1) { + break; + } + ctcss.process_audio_sample(sample); + } + fclose(fp); + + ASSERT_TRUE(ctcss.enough_samples()); + } + + void test_all_tones(GenerateSignal& signal, const float& tone = 0) { + for (auto standard_tone : CTCSS::standard_tones) { + // skipping tones within +/- 5Hz + if (abs(standard_tone - tone) < 5) { + continue; + } + + CTCSS ctcss(standard_tone, sample_rate, slow_window_size); + vector samples; + run_signal(ctcss, signal, samples); + + EXPECT_FALSE(ctcss.has_tone()) << "Tone of " << standard_tone << " found, expected " << tone; + + // on failure write out a file for debugging + if (ctcss.has_tone()) { + // double the samples to write to the file for later testing + size_t initial_count = samples.size(); + while (samples.size() < initial_count * 2) { + samples.push_back(signal.get_sample()); + } + + string filepath = "/tmp/found_" + to_string(standard_tone) + "_expected_" + to_string(tone); + write_file(samples, filepath); + } + } + if (tone != 0) { + CTCSS ctcss(tone, sample_rate, slow_window_size); + vector samples; + run_signal(ctcss, signal, samples); + + EXPECT_TRUE(ctcss.has_tone()) << "Expected tone of " << tone << " not found"; + + // on failure write out a file for debugging + if (!ctcss.has_tone()) { + // double the samples to write to the file for later testing + size_t initial_count = samples.size(); + while (samples.size() < initial_count * 2) { + samples.push_back(signal.get_sample()); + } + + string filepath = "/tmp/didnt_find_" + to_string(tone); + write_file(samples, filepath); + } + } + } + + void run_signal(CTCSS& ctcss, GenerateSignal& signal, vector& samples) { + EXPECT_TRUE(ctcss.is_enabled()) << "CTCSS not enabled"; + while (!ctcss.enough_samples()) { + float sample = signal.get_sample(); + samples.push_back(sample); + ctcss.process_audio_sample(sample); + } + } }; -TEST_F(CTCSSTest, creation) -{ - CTCSS ctcss; - EXPECT_FALSE(ctcss.is_enabled()); +TEST_F(CTCSSTest, creation) { + CTCSS ctcss; + EXPECT_FALSE(ctcss.is_enabled()); } -TEST_F(CTCSSTest, no_signal) -{ - GenerateSignal signal(sample_rate); - test_all_tones(signal); +TEST_F(CTCSSTest, no_signal) { + GenerateSignal signal(sample_rate); + test_all_tones(signal); } -TEST_F(CTCSSTest, has_tone) -{ - float tone = CTCSS::standard_tones[0]; - GenerateSignal signal(sample_rate); - signal.add_tone(tone, Tone::NORMAL); - signal.add_noise(Noise::NORMAL); - test_all_tones(signal, tone); +TEST_F(CTCSSTest, has_tone) { + float tone = CTCSS::standard_tones[0]; + GenerateSignal signal(sample_rate); + signal.add_tone(tone, Tone::NORMAL); + signal.add_noise(Noise::NORMAL); + test_all_tones(signal, tone); } -TEST_F(CTCSSTest, has_non_standard_tone) -{ - float tone = (CTCSS::standard_tones[0] + CTCSS::standard_tones[0]) / 2; - GenerateSignal signal(sample_rate); - signal.add_tone(tone, Tone::NORMAL); - signal.add_noise(Noise::NORMAL); - test_all_tones(signal, tone); +TEST_F(CTCSSTest, has_non_standard_tone) { + float tone = (CTCSS::standard_tones[0] + CTCSS::standard_tones[0]) / 2; + GenerateSignal signal(sample_rate); + signal.add_tone(tone, Tone::NORMAL); + signal.add_noise(Noise::NORMAL); + test_all_tones(signal, tone); } -TEST_F(CTCSSTest, has_each_standard_tone) -{ - for (auto tone : CTCSS::standard_tones) { - GenerateSignal signal(sample_rate); - signal.add_tone(tone, Tone::NORMAL); - signal.add_noise(Noise::NORMAL); - test_all_tones(signal, tone); - } +TEST_F(CTCSSTest, has_each_standard_tone) { + for (auto tone : CTCSS::standard_tones) { + GenerateSignal signal(sample_rate); + signal.add_tone(tone, Tone::NORMAL); + signal.add_noise(Noise::NORMAL); + test_all_tones(signal, tone); + } } diff --git a/src/test_filters.cpp b/src/test_filters.cpp index 8c2003e..770c924 100644 --- a/src/test_filters.cpp +++ b/src/test_filters.cpp @@ -23,28 +23,19 @@ using namespace std; -class FiltersTest : public TestBaseClass -{ -protected: - void SetUp(void) - { - TestBaseClass::SetUp(); - } +class FiltersTest : public TestBaseClass { + protected: + void SetUp(void) { TestBaseClass::SetUp(); } - void TearDown(void) - { - TestBaseClass::TearDown(); - } + void TearDown(void) { TestBaseClass::TearDown(); } }; -TEST_F(FiltersTest, default_notch) -{ - NotchFilter notch; - EXPECT_FALSE(notch.enabled()); +TEST_F(FiltersTest, default_notch) { + NotchFilter notch; + EXPECT_FALSE(notch.enabled()); } -TEST_F(FiltersTest, default_lowpass) -{ - LowpassFilter lowpass; - EXPECT_FALSE(lowpass.enabled()); +TEST_F(FiltersTest, default_lowpass) { + LowpassFilter lowpass; + EXPECT_FALSE(lowpass.enabled()); } diff --git a/src/test_generate_signal.cpp b/src/test_generate_signal.cpp index 0305fb4..3bb61fd 100644 --- a/src/test_generate_signal.cpp +++ b/src/test_generate_signal.cpp @@ -27,266 +27,254 @@ using namespace std; class ToneTest : public TestBaseClass {}; -TEST_F(ToneTest, simple_object) -{ - // simple case the sample rate is a multiple of the frequency so specific points can be measured - float tone_freq = 100; // tone at 100 Hz - // set sample rate to 1000 times the tone so there will be 250 samples per quarter - float sample_rate = 1000 * tone_freq; - float amplitude = Tone::STRONG; - - Tone tone(sample_rate, tone_freq, amplitude); - - float last_sample = 0.0; - float this_sample = 0.0; - - // loop through some number of cycles - for (int j = 0 ; j < 10 ; ++j) { - // first 249 samples will be positive and increasing - for (int i = 0; i < 249 ; ++i) { - this_sample = tone.get_sample(); - ASSERT_GT(this_sample, 0.0); - ASSERT_GT(this_sample, last_sample); - last_sample = this_sample; - } - - // sample 250 will be the amp - this_sample = tone.get_sample(); - ASSERT_EQ(this_sample, amplitude); - ASSERT_GT(this_sample, last_sample); - last_sample = this_sample; - - // next 249 samples will be positive and decreasing - for (int i = 0; i < 249 ; ++i) { - this_sample = tone.get_sample(); - ASSERT_GT(this_sample, 0.0); - ASSERT_LT(this_sample, last_sample); - last_sample = this_sample; - } - - // sample 500 will be zero-ish - this_sample = tone.get_sample(); - ASSERT_LT(this_sample, 0.000001); - ASSERT_LT(this_sample, last_sample); - last_sample = this_sample; - - // next 249 samples will be negative and decreasing - for (int i = 0; i < 249 ; ++i) { - this_sample = tone.get_sample(); - ASSERT_LT(this_sample, 0.0); - ASSERT_LT(this_sample, last_sample); - last_sample = this_sample; - } - - // sample 750 will be negative amp - this_sample = tone.get_sample(); - ASSERT_EQ(this_sample, -1.0 * amplitude); - ASSERT_LT(this_sample, last_sample); - last_sample = this_sample; - - // next 249 samples will be negative and increasing - for (int i = 0; i < 249 ; ++i) { - this_sample = tone.get_sample(); - ASSERT_LT(this_sample, 0.0); - ASSERT_GT(this_sample, last_sample); - last_sample = this_sample; - } - - // sample 1000 will be zero-ish - this_sample = tone.get_sample(); - ASSERT_LT(this_sample, 0.000001); - ASSERT_GT(this_sample, last_sample); - last_sample = this_sample; - } +TEST_F(ToneTest, simple_object) { + // simple case the sample rate is a multiple of the frequency so specific points can be measured + float tone_freq = 100; // tone at 100 Hz + // set sample rate to 1000 times the tone so there will be 250 samples per quarter + float sample_rate = 1000 * tone_freq; + float amplitude = Tone::STRONG; + + Tone tone(sample_rate, tone_freq, amplitude); + + float last_sample = 0.0; + float this_sample = 0.0; + + // loop through some number of cycles + for (int j = 0; j < 10; ++j) { + // first 249 samples will be positive and increasing + for (int i = 0; i < 249; ++i) { + this_sample = tone.get_sample(); + ASSERT_GT(this_sample, 0.0); + ASSERT_GT(this_sample, last_sample); + last_sample = this_sample; + } + + // sample 250 will be the amp + this_sample = tone.get_sample(); + ASSERT_EQ(this_sample, amplitude); + ASSERT_GT(this_sample, last_sample); + last_sample = this_sample; + + // next 249 samples will be positive and decreasing + for (int i = 0; i < 249; ++i) { + this_sample = tone.get_sample(); + ASSERT_GT(this_sample, 0.0); + ASSERT_LT(this_sample, last_sample); + last_sample = this_sample; + } + + // sample 500 will be zero-ish + this_sample = tone.get_sample(); + ASSERT_LT(this_sample, 0.000001); + ASSERT_LT(this_sample, last_sample); + last_sample = this_sample; + + // next 249 samples will be negative and decreasing + for (int i = 0; i < 249; ++i) { + this_sample = tone.get_sample(); + ASSERT_LT(this_sample, 0.0); + ASSERT_LT(this_sample, last_sample); + last_sample = this_sample; + } + + // sample 750 will be negative amp + this_sample = tone.get_sample(); + ASSERT_EQ(this_sample, -1.0 * amplitude); + ASSERT_LT(this_sample, last_sample); + last_sample = this_sample; + + // next 249 samples will be negative and increasing + for (int i = 0; i < 249; ++i) { + this_sample = tone.get_sample(); + ASSERT_LT(this_sample, 0.0); + ASSERT_GT(this_sample, last_sample); + last_sample = this_sample; + } + + // sample 1000 will be zero-ish + this_sample = tone.get_sample(); + ASSERT_LT(this_sample, 0.000001); + ASSERT_GT(this_sample, last_sample); + last_sample = this_sample; + } } TEST_F(ToneTest, strengths) { - float tone_freq = 100; - float sample_rate = 8000; - - Tone tone_weak(sample_rate, tone_freq, Tone::WEAK); - Tone tone_normal(sample_rate, tone_freq, Tone::NORMAL); - Tone tone_strong(sample_rate, tone_freq, Tone::STRONG); - - for (int i = 0; i < 100 * sample_rate; ++i) { - float weak_sample = tone_weak.get_sample(); - float normal_sample = tone_normal.get_sample(); - float strong_sample = tone_strong.get_sample(); - - if (weak_sample > 0.0) { - ASSERT_LT(weak_sample, normal_sample); - ASSERT_LT(normal_sample, strong_sample); - } else if (weak_sample == 0.0) { - ASSERT_EQ(weak_sample, 0.0); - ASSERT_EQ(normal_sample, 0.0); - ASSERT_EQ(strong_sample, 0.0); - } else { - ASSERT_GT(weak_sample, normal_sample); - ASSERT_GT(normal_sample, strong_sample); - } - } + float tone_freq = 100; + float sample_rate = 8000; + + Tone tone_weak(sample_rate, tone_freq, Tone::WEAK); + Tone tone_normal(sample_rate, tone_freq, Tone::NORMAL); + Tone tone_strong(sample_rate, tone_freq, Tone::STRONG); + + for (int i = 0; i < 100 * sample_rate; ++i) { + float weak_sample = tone_weak.get_sample(); + float normal_sample = tone_normal.get_sample(); + float strong_sample = tone_strong.get_sample(); + + if (weak_sample > 0.0) { + ASSERT_LT(weak_sample, normal_sample); + ASSERT_LT(normal_sample, strong_sample); + } else if (weak_sample == 0.0) { + ASSERT_EQ(weak_sample, 0.0); + ASSERT_EQ(normal_sample, 0.0); + ASSERT_EQ(strong_sample, 0.0); + } else { + ASSERT_GT(weak_sample, normal_sample); + ASSERT_GT(normal_sample, strong_sample); + } + } } - class NoiseTest : public TestBaseClass {}; - -TEST_F(NoiseTest, simple_object) -{ - Noise noise(Noise::STRONG); - - int sample_count = 10000; - float sample_max = 0.0; - float sample_min = 0.0; - float sample_sum = 0.0; - for (int i = 0 ; i < sample_count ; ++i) { - float sample = noise.get_sample(); - sample_max = max(sample, sample_max); - sample_min = min(sample, sample_min); - sample_sum += sample; - } - float sample_avg = sample_sum / sample_count; - - // average is near zero - EXPECT_LE(abs(sample_avg), 0.01); - // max and min are off of zero - EXPECT_LE(sample_min, Noise::STRONG * -0.3); - EXPECT_GT(sample_max, Noise::STRONG * 0.3); +TEST_F(NoiseTest, simple_object) { + Noise noise(Noise::STRONG); + + int sample_count = 10000; + float sample_max = 0.0; + float sample_min = 0.0; + float sample_sum = 0.0; + for (int i = 0; i < sample_count; ++i) { + float sample = noise.get_sample(); + sample_max = max(sample, sample_max); + sample_min = min(sample, sample_min); + sample_sum += sample; + } + float sample_avg = sample_sum / sample_count; + + // average is near zero + EXPECT_LE(abs(sample_avg), 0.01); + // max and min are off of zero + EXPECT_LE(sample_min, Noise::STRONG * -0.3); + EXPECT_GT(sample_max, Noise::STRONG * 0.3); } -TEST_F(NoiseTest, strengths) -{ - Noise noise_weak(Noise::WEAK); - Noise noise_normal(Noise::NORMAL); - Noise noise_strong(Noise::STRONG); - - float weak_max = 0.0; - float normal_max = 0.0; - float strong_max = 0.0; - for (int i = 0 ; i < 10000 ; ++i) { - weak_max = max(weak_max, abs(noise_weak.get_sample())); - normal_max = max(normal_max, abs(noise_normal.get_sample())); - strong_max = max(strong_max, abs(noise_strong.get_sample())); - } - - EXPECT_NE(weak_max, 0.0); - EXPECT_GT(normal_max, weak_max); - EXPECT_GT(strong_max, normal_max); +TEST_F(NoiseTest, strengths) { + Noise noise_weak(Noise::WEAK); + Noise noise_normal(Noise::NORMAL); + Noise noise_strong(Noise::STRONG); + + float weak_max = 0.0; + float normal_max = 0.0; + float strong_max = 0.0; + for (int i = 0; i < 10000; ++i) { + weak_max = max(weak_max, abs(noise_weak.get_sample())); + normal_max = max(normal_max, abs(noise_normal.get_sample())); + strong_max = max(strong_max, abs(noise_strong.get_sample())); + } + + EXPECT_NE(weak_max, 0.0); + EXPECT_GT(normal_max, weak_max); + EXPECT_GT(strong_max, normal_max); } class GenerateSignalTest : public TestBaseClass { -protected: - int sample_rate; - void SetUp(void) { - TestBaseClass::SetUp(); - sample_rate = 8000; - } + protected: + int sample_rate; + void SetUp(void) { + TestBaseClass::SetUp(); + sample_rate = 8000; + } }; -TEST_F(GenerateSignalTest, default_object) -{ - GenerateSignal signal(8000); - EXPECT_EQ(signal.get_sample(), 0.0); +TEST_F(GenerateSignalTest, default_object) { + GenerateSignal signal(8000); + EXPECT_EQ(signal.get_sample(), 0.0); } TEST_F(GenerateSignalTest, generate_file) { - - float file_seconds = 10.5; - GenerateSignal signal(sample_rate); + float file_seconds = 10.5; + GenerateSignal signal(sample_rate); - string test_filepath = temp_dir + "/10_sec_file.dat"; - signal.write_file(test_filepath, file_seconds); - - // make sure the file exists and is the right size - struct stat info; - ASSERT_EQ(stat(test_filepath.c_str(), &info), 0); - EXPECT_TRUE(S_ISREG(info.st_mode)); - EXPECT_EQ(info.st_size, sample_rate * file_seconds * sizeof(float)); + string test_filepath = temp_dir + "/10_sec_file.dat"; + signal.write_file(test_filepath, file_seconds); + // make sure the file exists and is the right size + struct stat info; + ASSERT_EQ(stat(test_filepath.c_str(), &info), 0); + EXPECT_TRUE(S_ISREG(info.st_mode)); + EXPECT_EQ(info.st_size, sample_rate * file_seconds * sizeof(float)); } TEST_F(GenerateSignalTest, get_sample_no_signals) { - GenerateSignal signal(sample_rate); - for (int i = 0 ; i < 60 * sample_rate ; ++i) { - ASSERT_EQ(signal.get_sample(), 0.0); - } + GenerateSignal signal(sample_rate); + for (int i = 0; i < 60 * sample_rate; ++i) { + ASSERT_EQ(signal.get_sample(), 0.0); + } } TEST_F(GenerateSignalTest, get_sample_single_tone_only) { - float tone_freq = 123.34; - float tone_ampl = 0.32; - - GenerateSignal signal(sample_rate); - signal.add_tone(tone_freq, tone_ampl); - Tone tone(sample_rate, tone_freq, tone_ampl); - for (int i = 0 ; i < 60 * sample_rate ; ++i) { - ASSERT_FLOAT_EQ(signal.get_sample(), tone.get_sample()); - } + float tone_freq = 123.34; + float tone_ampl = 0.32; + + GenerateSignal signal(sample_rate); + signal.add_tone(tone_freq, tone_ampl); + Tone tone(sample_rate, tone_freq, tone_ampl); + for (int i = 0; i < 60 * sample_rate; ++i) { + ASSERT_FLOAT_EQ(signal.get_sample(), tone.get_sample()); + } } TEST_F(GenerateSignalTest, get_sample_two_tones) { - float tone1_freq = 123.34; - float tone2_freq = 231.43; - float tone1_ampl = Tone::NORMAL; - float tone2_ampl = Tone::STRONG; - - GenerateSignal signal(sample_rate); - signal.add_tone(tone1_freq, tone1_ampl); - signal.add_tone(tone2_freq, tone2_ampl); - Tone tone1(sample_rate, tone1_freq, tone1_ampl); - Tone tone2(sample_rate, tone2_freq, tone2_ampl); - for (int i = 0 ; i < 60 * sample_rate ; ++i) { - ASSERT_NEAR(signal.get_sample(), tone1.get_sample() + tone2.get_sample(), 0.000001); - } + float tone1_freq = 123.34; + float tone2_freq = 231.43; + float tone1_ampl = Tone::NORMAL; + float tone2_ampl = Tone::STRONG; + + GenerateSignal signal(sample_rate); + signal.add_tone(tone1_freq, tone1_ampl); + signal.add_tone(tone2_freq, tone2_ampl); + Tone tone1(sample_rate, tone1_freq, tone1_ampl); + Tone tone2(sample_rate, tone2_freq, tone2_ampl); + for (int i = 0; i < 60 * sample_rate; ++i) { + ASSERT_NEAR(signal.get_sample(), tone1.get_sample() + tone2.get_sample(), 0.000001); + } } TEST_F(GenerateSignalTest, get_sample_only_noise) { - - GenerateSignal signal(sample_rate); - signal.add_noise(Noise::NORMAL); - - float max_value = 0; - float min_value = 0; - for (int i = 0 ; i < 600 * sample_rate ; ++i) { - float sample = signal.get_sample(); - min_value = min(sample, min_value); - max_value = max(sample, max_value); - } - - EXPECT_GT(max_value, 0); - EXPECT_LT(max_value, Noise::NORMAL); - - EXPECT_LT(min_value, 0); - EXPECT_GT(min_value, -1.0 * Noise::NORMAL); + GenerateSignal signal(sample_rate); + signal.add_noise(Noise::NORMAL); + + float max_value = 0; + float min_value = 0; + for (int i = 0; i < 600 * sample_rate; ++i) { + float sample = signal.get_sample(); + min_value = min(sample, min_value); + max_value = max(sample, max_value); + } + + EXPECT_GT(max_value, 0); + EXPECT_LT(max_value, Noise::NORMAL); + + EXPECT_LT(min_value, 0); + EXPECT_GT(min_value, -1.0 * Noise::NORMAL); } TEST_F(GenerateSignalTest, get_sample_two_tones_and_noise) { - - float tone1_freq = 123.34; - float tone2_freq = 231.43; - float tone1_ampl = Tone::NORMAL; - float tone2_ampl = Tone::WEAK; - - GenerateSignal signal(sample_rate); - signal.add_tone(tone1_freq, tone1_ampl); - signal.add_tone(tone2_freq, tone2_ampl); - signal.add_noise(Noise::NORMAL); - - Tone tone1(sample_rate, tone1_freq, tone1_ampl); - Tone tone2(sample_rate, tone2_freq, tone2_ampl); - float max_value = 0; - float min_value = 0; - for (int i = 0 ; i < 60 * sample_rate ; ++i) { - float sample_noise = signal.get_sample() - tone1.get_sample() - tone2.get_sample(); - min_value = min(sample_noise, min_value); - max_value = max(sample_noise, max_value); - - } - - EXPECT_GT(max_value, 0); - EXPECT_LT(max_value, Noise::NORMAL); - - EXPECT_LT(min_value, 0); - EXPECT_GT(min_value, -1.0 * Noise::NORMAL); + float tone1_freq = 123.34; + float tone2_freq = 231.43; + float tone1_ampl = Tone::NORMAL; + float tone2_ampl = Tone::WEAK; + + GenerateSignal signal(sample_rate); + signal.add_tone(tone1_freq, tone1_ampl); + signal.add_tone(tone2_freq, tone2_ampl); + signal.add_noise(Noise::NORMAL); + + Tone tone1(sample_rate, tone1_freq, tone1_ampl); + Tone tone2(sample_rate, tone2_freq, tone2_ampl); + float max_value = 0; + float min_value = 0; + for (int i = 0; i < 60 * sample_rate; ++i) { + float sample_noise = signal.get_sample() - tone1.get_sample() - tone2.get_sample(); + min_value = min(sample_noise, min_value); + max_value = max(sample_noise, max_value); + } + + EXPECT_GT(max_value, 0); + EXPECT_LT(max_value, Noise::NORMAL); + + EXPECT_LT(min_value, 0); + EXPECT_GT(min_value, -1.0 * Noise::NORMAL); } - diff --git a/src/test_helper_functions.cpp b/src/test_helper_functions.cpp index fc53c17..9f78172 100644 --- a/src/test_helper_functions.cpp +++ b/src/test_helper_functions.cpp @@ -24,168 +24,144 @@ using namespace std; class HelperFunctionsTest : public TestBaseClass { -protected: + protected: + void SetUp(void) { TestBaseClass::SetUp(); } - void SetUp(void) { - TestBaseClass::SetUp(); - } - - void create_file(const string &filepath) { - fclose(fopen(filepath.c_str(), "wb")); - EXPECT_TRUE(file_exists(filepath)); - } + void create_file(const string& filepath) { + fclose(fopen(filepath.c_str(), "wb")); + EXPECT_TRUE(file_exists(filepath)); + } }; -TEST_F(HelperFunctionsTest, dir_exists_true) -{ - EXPECT_TRUE(dir_exists(temp_dir)); +TEST_F(HelperFunctionsTest, dir_exists_true) { + EXPECT_TRUE(dir_exists(temp_dir)); } -TEST_F(HelperFunctionsTest, dir_exists_false) -{ - EXPECT_FALSE(dir_exists("/not/a/real/dir")); +TEST_F(HelperFunctionsTest, dir_exists_false) { + EXPECT_FALSE(dir_exists("/not/a/real/dir")); } -TEST_F(HelperFunctionsTest, dir_exists_not_dir) -{ - string file_in_dir = temp_dir + "/some_file"; - create_file(file_in_dir); - EXPECT_FALSE(dir_exists(file_in_dir)); +TEST_F(HelperFunctionsTest, dir_exists_not_dir) { + string file_in_dir = temp_dir + "/some_file"; + create_file(file_in_dir); + EXPECT_FALSE(dir_exists(file_in_dir)); } -TEST_F(HelperFunctionsTest, file_exists_true) -{ - string file_in_dir = temp_dir + "/some_file"; - create_file(file_in_dir); - EXPECT_TRUE(file_exists(file_in_dir)); +TEST_F(HelperFunctionsTest, file_exists_true) { + string file_in_dir = temp_dir + "/some_file"; + create_file(file_in_dir); + EXPECT_TRUE(file_exists(file_in_dir)); } -TEST_F(HelperFunctionsTest, file_exists_false) -{ - EXPECT_FALSE(file_exists(temp_dir + "/nothing")); +TEST_F(HelperFunctionsTest, file_exists_false) { + EXPECT_FALSE(file_exists(temp_dir + "/nothing")); } -TEST_F(HelperFunctionsTest, file_exists_not_file) -{ - EXPECT_FALSE(file_exists(temp_dir)); - EXPECT_TRUE(dir_exists(temp_dir)); +TEST_F(HelperFunctionsTest, file_exists_not_file) { + EXPECT_FALSE(file_exists(temp_dir)); + EXPECT_TRUE(dir_exists(temp_dir)); } - -TEST_F(HelperFunctionsTest, make_dir_normal) -{ - const string dir_path = temp_dir + "/a"; - EXPECT_FALSE(dir_exists(dir_path)); - EXPECT_TRUE(make_dir(dir_path)); - EXPECT_TRUE(dir_exists(dir_path)); +TEST_F(HelperFunctionsTest, make_dir_normal) { + const string dir_path = temp_dir + "/a"; + EXPECT_FALSE(dir_exists(dir_path)); + EXPECT_TRUE(make_dir(dir_path)); + EXPECT_TRUE(dir_exists(dir_path)); } -TEST_F(HelperFunctionsTest, make_dir_exists) -{ - EXPECT_TRUE(dir_exists(temp_dir)); - EXPECT_TRUE(make_dir(temp_dir)); - EXPECT_TRUE(dir_exists(temp_dir)); +TEST_F(HelperFunctionsTest, make_dir_exists) { + EXPECT_TRUE(dir_exists(temp_dir)); + EXPECT_TRUE(make_dir(temp_dir)); + EXPECT_TRUE(dir_exists(temp_dir)); } -TEST_F(HelperFunctionsTest, make_dir_empty) -{ - EXPECT_FALSE(make_dir("")); +TEST_F(HelperFunctionsTest, make_dir_empty) { + EXPECT_FALSE(make_dir("")); } -TEST_F(HelperFunctionsTest, make_dir_fail) -{ - EXPECT_FALSE(make_dir("/this/path/does/not/exist")); +TEST_F(HelperFunctionsTest, make_dir_fail) { + EXPECT_FALSE(make_dir("/this/path/does/not/exist")); } -TEST_F(HelperFunctionsTest, make_dir_file_in_the_way) -{ - const string file_path = temp_dir + "/some_file"; - create_file(file_path); - EXPECT_FALSE(make_dir(file_path)); +TEST_F(HelperFunctionsTest, make_dir_file_in_the_way) { + const string file_path = temp_dir + "/some_file"; + create_file(file_path); + EXPECT_FALSE(make_dir(file_path)); } -TEST_F(HelperFunctionsTest, make_subdirs_exists) -{ - EXPECT_TRUE(dir_exists(temp_dir)); - EXPECT_TRUE(make_subdirs(temp_dir, "")); - EXPECT_TRUE(dir_exists(temp_dir)); +TEST_F(HelperFunctionsTest, make_subdirs_exists) { + EXPECT_TRUE(dir_exists(temp_dir)); + EXPECT_TRUE(make_subdirs(temp_dir, "")); + EXPECT_TRUE(dir_exists(temp_dir)); } -TEST_F(HelperFunctionsTest, make_subdirs_one_subdir) -{ - const string path = "bob"; - EXPECT_FALSE(dir_exists(temp_dir + "/" + path)); - EXPECT_TRUE(make_subdirs(temp_dir, path)); - EXPECT_TRUE(dir_exists(temp_dir + "/" + path)); +TEST_F(HelperFunctionsTest, make_subdirs_one_subdir) { + const string path = "bob"; + EXPECT_FALSE(dir_exists(temp_dir + "/" + path)); + EXPECT_TRUE(make_subdirs(temp_dir, path)); + EXPECT_TRUE(dir_exists(temp_dir + "/" + path)); } -TEST_F(HelperFunctionsTest, make_subdirs_multiple_subdir) -{ - const string path = "bob/joe/sam"; - EXPECT_FALSE(dir_exists(temp_dir + "/" + path)); - EXPECT_TRUE(make_subdirs(temp_dir, path)); - EXPECT_TRUE(dir_exists(temp_dir + "/" + path)); +TEST_F(HelperFunctionsTest, make_subdirs_multiple_subdir) { + const string path = "bob/joe/sam"; + EXPECT_FALSE(dir_exists(temp_dir + "/" + path)); + EXPECT_TRUE(make_subdirs(temp_dir, path)); + EXPECT_TRUE(dir_exists(temp_dir + "/" + path)); } -TEST_F(HelperFunctionsTest, make_subdirs_file_in_the_way) -{ - const string file_in_dir = temp_dir + "/some_file"; - create_file(file_in_dir); - EXPECT_TRUE(file_exists(file_in_dir)); - EXPECT_FALSE(make_subdirs(temp_dir, "some_file/some_dir")); - EXPECT_FALSE(dir_exists(file_in_dir)); - EXPECT_TRUE(file_exists(file_in_dir)); +TEST_F(HelperFunctionsTest, make_subdirs_file_in_the_way) { + const string file_in_dir = temp_dir + "/some_file"; + create_file(file_in_dir); + EXPECT_TRUE(file_exists(file_in_dir)); + EXPECT_FALSE(make_subdirs(temp_dir, "some_file/some_dir")); + EXPECT_FALSE(dir_exists(file_in_dir)); + EXPECT_TRUE(file_exists(file_in_dir)); } -TEST_F(HelperFunctionsTest, make_subdirs_create_base) -{ - EXPECT_FALSE(dir_exists(temp_dir + "/base_dir/a")); - EXPECT_TRUE(make_subdirs(temp_dir + "/base_dir", "a")); - EXPECT_TRUE(dir_exists(temp_dir + "/base_dir/a")); +TEST_F(HelperFunctionsTest, make_subdirs_create_base) { + EXPECT_FALSE(dir_exists(temp_dir + "/base_dir/a")); + EXPECT_TRUE(make_subdirs(temp_dir + "/base_dir", "a")); + EXPECT_TRUE(dir_exists(temp_dir + "/base_dir/a")); } -TEST_F(HelperFunctionsTest, make_subdirs_extra_slashes) -{ - EXPECT_FALSE(dir_exists(temp_dir + "/a/b/c/d")); - EXPECT_TRUE(make_subdirs(temp_dir, "///a/b////c///d")); - EXPECT_TRUE(dir_exists(temp_dir + "/a/b/c/d")); +TEST_F(HelperFunctionsTest, make_subdirs_extra_slashes) { + EXPECT_FALSE(dir_exists(temp_dir + "/a/b/c/d")); + EXPECT_TRUE(make_subdirs(temp_dir, "///a/b////c///d")); + EXPECT_TRUE(dir_exists(temp_dir + "/a/b/c/d")); } -TEST_F(HelperFunctionsTest, make_dated_subdirs_normal) -{ - struct tm time_struct; +TEST_F(HelperFunctionsTest, make_dated_subdirs_normal) { + struct tm time_struct; - strptime("2010-3-7", "%Y-%m-%d", &time_struct); + strptime("2010-3-7", "%Y-%m-%d", &time_struct); - const string dir_path = temp_dir + "/2010/03/07"; + const string dir_path = temp_dir + "/2010/03/07"; - EXPECT_FALSE(dir_exists(dir_path)); - EXPECT_EQ(make_dated_subdirs(temp_dir, &time_struct), dir_path); - EXPECT_TRUE(dir_exists(dir_path)); + EXPECT_FALSE(dir_exists(dir_path)); + EXPECT_EQ(make_dated_subdirs(temp_dir, &time_struct), dir_path); + EXPECT_TRUE(dir_exists(dir_path)); } -TEST_F(HelperFunctionsTest, make_dated_subdirs_fail) -{ - struct tm time_struct; +TEST_F(HelperFunctionsTest, make_dated_subdirs_fail) { + struct tm time_struct; - strptime("2010-3-7", "%Y-%m-%d", &time_struct); + strptime("2010-3-7", "%Y-%m-%d", &time_struct); - EXPECT_EQ(make_dated_subdirs("/invalid/base/dir", &time_struct), ""); + EXPECT_EQ(make_dated_subdirs("/invalid/base/dir", &time_struct), ""); } -TEST_F(HelperFunctionsTest, make_dated_subdirs_some_exist) -{ - struct tm time_struct; +TEST_F(HelperFunctionsTest, make_dated_subdirs_some_exist) { + struct tm time_struct; - const string dir_through_month = temp_dir + "/2010/03/"; + const string dir_through_month = temp_dir + "/2010/03/"; - strptime("2010-3-7", "%Y-%m-%d", &time_struct); - EXPECT_EQ(make_dated_subdirs(temp_dir, &time_struct), dir_through_month + "07"); + strptime("2010-3-7", "%Y-%m-%d", &time_struct); + EXPECT_EQ(make_dated_subdirs(temp_dir, &time_struct), dir_through_month + "07"); - EXPECT_TRUE(dir_exists(dir_through_month)); - EXPECT_FALSE(dir_exists(dir_through_month + "08")); + EXPECT_TRUE(dir_exists(dir_through_month)); + EXPECT_FALSE(dir_exists(dir_through_month + "08")); - strptime("2010-3-8", "%Y-%m-%d", &time_struct); - EXPECT_EQ(make_dated_subdirs(temp_dir, &time_struct), dir_through_month + "08"); - EXPECT_TRUE(dir_exists(dir_through_month + "08")); + strptime("2010-3-8", "%Y-%m-%d", &time_struct); + EXPECT_EQ(make_dated_subdirs(temp_dir, &time_struct), dir_through_month + "08"); + EXPECT_TRUE(dir_exists(dir_through_month + "08")); } diff --git a/src/test_squelch.cpp b/src/test_squelch.cpp index c718495..098bfb0 100644 --- a/src/test_squelch.cpp +++ b/src/test_squelch.cpp @@ -17,275 +17,265 @@ * along with this program. If not, see . */ -#include "test_base_class.h" #include "generate_signal.h" +#include "test_base_class.h" #include "squelch.h" using namespace std; class SquelchTest : public TestBaseClass { -protected: - void SetUp(void) - { - TestBaseClass::SetUp(); - - raw_no_signal_sample = 0.05; - raw_signal_sample = 0.75; - } - - void TearDown(void) - { - TestBaseClass::TearDown(); - } - - // send through "no signal" samples to get noise floor down - void send_samples_for_noise_floor(Squelch &squelch) { - while (squelch.noise_level() > 1.01 * raw_no_signal_sample) { - squelch.process_raw_sample(raw_no_signal_sample); - } - ASSERT_LE(squelch.noise_level(), 1.01 * raw_no_signal_sample); - ASSERT_GT(raw_signal_sample, squelch.squelch_level()); - } - - float raw_no_signal_sample; - float raw_signal_sample; + protected: + void SetUp(void) { + TestBaseClass::SetUp(); + + raw_no_signal_sample = 0.05; + raw_signal_sample = 0.75; + } + + void TearDown(void) { TestBaseClass::TearDown(); } + + // send through "no signal" samples to get noise floor down + void send_samples_for_noise_floor(Squelch& squelch) { + while (squelch.noise_level() > 1.01 * raw_no_signal_sample) { + squelch.process_raw_sample(raw_no_signal_sample); + } + ASSERT_LE(squelch.noise_level(), 1.01 * raw_no_signal_sample); + ASSERT_GT(raw_signal_sample, squelch.squelch_level()); + } + + float raw_no_signal_sample; + float raw_signal_sample; }; TEST_F(SquelchTest, default_object) { - Squelch squelch; - EXPECT_EQ(squelch.open_count(), 0); + Squelch squelch; + EXPECT_EQ(squelch.open_count(), 0); } TEST_F(SquelchTest, noise_floor) { - Squelch squelch; - - // noise floor starts high - EXPECT_GT(squelch.noise_level(), 10.0 * raw_no_signal_sample); - - // noise floor drifts down towards (but never at) the incoming raw sample level - float last_noise_level, this_noise_level; - this_noise_level = squelch.noise_level(); - do - { - last_noise_level = this_noise_level; - - // not all samples update noise floor - for (int j = 0; j < 25; ++j) { - squelch.process_raw_sample(raw_no_signal_sample); - } - - this_noise_level = squelch.noise_level(); - ASSERT_LE(this_noise_level, last_noise_level); - } while (this_noise_level != last_noise_level); - - // noise floor ends up close to the incoming level - EXPECT_LT(squelch.noise_level(), 1.01 * raw_no_signal_sample); + Squelch squelch; + + // noise floor starts high + EXPECT_GT(squelch.noise_level(), 10.0 * raw_no_signal_sample); + + // noise floor drifts down towards (but never at) the incoming raw sample level + float last_noise_level, this_noise_level; + this_noise_level = squelch.noise_level(); + do { + last_noise_level = this_noise_level; + + // not all samples update noise floor + for (int j = 0; j < 25; ++j) { + squelch.process_raw_sample(raw_no_signal_sample); + } + this_noise_level = squelch.noise_level(); + ASSERT_LE(this_noise_level, last_noise_level); + } while (this_noise_level != last_noise_level); + + // noise floor ends up close to the incoming level + EXPECT_LT(squelch.noise_level(), 1.01 * raw_no_signal_sample); } TEST_F(SquelchTest, normal_operation) { - Squelch squelch; - - // send through "no signal" samples to get noise floor down - send_samples_for_noise_floor(squelch); - ASSERT_LE(squelch.noise_level(), 1.01 * raw_no_signal_sample); - ASSERT_GT(raw_signal_sample, squelch.squelch_level()); - - // send through "signal" samples and squelch should open shortly - for(int i = 0; i < 500 && !squelch.is_open() ; ++i) { - squelch.process_raw_sample(raw_signal_sample); - } - ASSERT_TRUE(squelch.is_open()); - ASSERT_TRUE(squelch.should_process_audio()); - - // send through a bunch more "signal" values and squelch stays open - for(int i = 0; i < 1000 ; ++i) { - squelch.process_raw_sample(raw_signal_sample); - } - ASSERT_TRUE(squelch.is_open()); - ASSERT_TRUE(squelch.should_process_audio()); - - // send through "no signal" samples and squelch should close quickly - for(int i = 0; i < 100 && squelch.is_open() ; ++i) { - squelch.process_raw_sample(raw_no_signal_sample); - } - ASSERT_FALSE(squelch.is_open()); - ASSERT_FALSE(squelch.should_process_audio()); + Squelch squelch; + + // send through "no signal" samples to get noise floor down + send_samples_for_noise_floor(squelch); + ASSERT_LE(squelch.noise_level(), 1.01 * raw_no_signal_sample); + ASSERT_GT(raw_signal_sample, squelch.squelch_level()); + + // send through "signal" samples and squelch should open shortly + for (int i = 0; i < 500 && !squelch.is_open(); ++i) { + squelch.process_raw_sample(raw_signal_sample); + } + ASSERT_TRUE(squelch.is_open()); + ASSERT_TRUE(squelch.should_process_audio()); + + // send through a bunch more "signal" values and squelch stays open + for (int i = 0; i < 1000; ++i) { + squelch.process_raw_sample(raw_signal_sample); + } + ASSERT_TRUE(squelch.is_open()); + ASSERT_TRUE(squelch.should_process_audio()); + + // send through "no signal" samples and squelch should close quickly + for (int i = 0; i < 100 && squelch.is_open(); ++i) { + squelch.process_raw_sample(raw_no_signal_sample); + } + ASSERT_FALSE(squelch.is_open()); + ASSERT_FALSE(squelch.should_process_audio()); } - TEST_F(SquelchTest, dead_spot) { - Squelch squelch; - - send_samples_for_noise_floor(squelch); - - // send through "signal" samples and squelch should open shortly - for(int i = 0; i < 500 && !squelch.is_open() ; ++i) { - squelch.process_raw_sample(raw_signal_sample); - } - ASSERT_TRUE(squelch.is_open()); - ASSERT_TRUE(squelch.should_process_audio()); - - // send through a bunch more "signal" values and squelch stays open - for(int i = 0; i < 1000 ; ++i) { - squelch.process_raw_sample(raw_signal_sample); - } - ASSERT_TRUE(squelch.is_open()); - ASSERT_TRUE(squelch.should_process_audio()); - - // send through a dead spot of "no signal" and squelch should stay open - for(int i = 0; i < 50; ++i) { - squelch.process_raw_sample(raw_no_signal_sample); - ASSERT_TRUE(squelch.is_open()); - ASSERT_TRUE(squelch.should_process_audio()); - } - - // send go back to "signal" samples and squelch stays open - for(int i = 0; i < 1000 ; ++i) { - squelch.process_raw_sample(raw_signal_sample); - ASSERT_TRUE(squelch.is_open()); - ASSERT_TRUE(squelch.should_process_audio()); - } + Squelch squelch; + + send_samples_for_noise_floor(squelch); + + // send through "signal" samples and squelch should open shortly + for (int i = 0; i < 500 && !squelch.is_open(); ++i) { + squelch.process_raw_sample(raw_signal_sample); + } + ASSERT_TRUE(squelch.is_open()); + ASSERT_TRUE(squelch.should_process_audio()); + + // send through a bunch more "signal" values and squelch stays open + for (int i = 0; i < 1000; ++i) { + squelch.process_raw_sample(raw_signal_sample); + } + ASSERT_TRUE(squelch.is_open()); + ASSERT_TRUE(squelch.should_process_audio()); + + // send through a dead spot of "no signal" and squelch should stay open + for (int i = 0; i < 50; ++i) { + squelch.process_raw_sample(raw_no_signal_sample); + ASSERT_TRUE(squelch.is_open()); + ASSERT_TRUE(squelch.should_process_audio()); + } + + // send go back to "signal" samples and squelch stays open + for (int i = 0; i < 1000; ++i) { + squelch.process_raw_sample(raw_signal_sample); + ASSERT_TRUE(squelch.is_open()); + ASSERT_TRUE(squelch.should_process_audio()); + } } TEST_F(SquelchTest, should_process_audio) { - Squelch squelch; - - send_samples_for_noise_floor(squelch); - - // should_process_audio is true as soon as squelch opens - for(int i = 0; i < 500 && !squelch.is_open() ; ++i) { - ASSERT_FALSE(squelch.should_process_audio()); - squelch.process_raw_sample(raw_signal_sample); - } - ASSERT_TRUE(squelch.is_open()); - ASSERT_TRUE(squelch.should_process_audio()); - - // and stays true until fully closed - for(int i = 0; i < 100 && squelch.is_open() ; ++i) { - ASSERT_TRUE(squelch.should_process_audio()); - squelch.process_raw_sample(raw_no_signal_sample); - } - ASSERT_FALSE(squelch.is_open()); - ASSERT_FALSE(squelch.should_process_audio()); + Squelch squelch; + + send_samples_for_noise_floor(squelch); + + // should_process_audio is true as soon as squelch opens + for (int i = 0; i < 500 && !squelch.is_open(); ++i) { + ASSERT_FALSE(squelch.should_process_audio()); + squelch.process_raw_sample(raw_signal_sample); + } + ASSERT_TRUE(squelch.is_open()); + ASSERT_TRUE(squelch.should_process_audio()); + + // and stays true until fully closed + for (int i = 0; i < 100 && squelch.is_open(); ++i) { + ASSERT_TRUE(squelch.should_process_audio()); + squelch.process_raw_sample(raw_no_signal_sample); + } + ASSERT_FALSE(squelch.is_open()); + ASSERT_FALSE(squelch.should_process_audio()); } TEST_F(SquelchTest, good_ctcss) { - - float tone = CTCSS::standard_tones[5]; - float sample_rate = 8000; - - Squelch squelch; - squelch.set_ctcss_freq(tone, sample_rate); - send_samples_for_noise_floor(squelch); - - GenerateSignal signal_with_tone(sample_rate); - signal_with_tone.add_tone(tone, Tone::NORMAL); - - // send through "signal" samples until its time to process audio - for(int i = 0; i < 500 && !squelch.should_process_audio() ; ++i) { - squelch.process_raw_sample(raw_signal_sample); - } - ASSERT_FALSE(squelch.is_open()); - ASSERT_TRUE(squelch.should_process_audio()); - - // process audio samples and "signal" samples until squelch is open - for(int i = 0; i < 500 && !squelch.is_open() ; ++i) { - squelch.process_audio_sample(signal_with_tone.get_sample()); - squelch.process_raw_sample(raw_signal_sample); - } - ASSERT_TRUE(squelch.is_open()); - ASSERT_TRUE(squelch.should_process_audio()); - - // run through a lot more to ensure squelch stays open - for(int i = 0; i < 100000; ++i) { - squelch.process_audio_sample(signal_with_tone.get_sample()); - squelch.process_raw_sample(raw_signal_sample); - ASSERT_TRUE(squelch.is_open()); - ASSERT_TRUE(squelch.should_process_audio()); - } - - EXPECT_GT(squelch.ctcss_count(), 0); - EXPECT_EQ(squelch.no_ctcss_count(), 0); + float tone = CTCSS::standard_tones[5]; + float sample_rate = 8000; + + Squelch squelch; + squelch.set_ctcss_freq(tone, sample_rate); + send_samples_for_noise_floor(squelch); + + GenerateSignal signal_with_tone(sample_rate); + signal_with_tone.add_tone(tone, Tone::NORMAL); + + // send through "signal" samples until its time to process audio + for (int i = 0; i < 500 && !squelch.should_process_audio(); ++i) { + squelch.process_raw_sample(raw_signal_sample); + } + ASSERT_FALSE(squelch.is_open()); + ASSERT_TRUE(squelch.should_process_audio()); + + // process audio samples and "signal" samples until squelch is open + for (int i = 0; i < 500 && !squelch.is_open(); ++i) { + squelch.process_audio_sample(signal_with_tone.get_sample()); + squelch.process_raw_sample(raw_signal_sample); + } + ASSERT_TRUE(squelch.is_open()); + ASSERT_TRUE(squelch.should_process_audio()); + + // run through a lot more to ensure squelch stays open + for (int i = 0; i < 100000; ++i) { + squelch.process_audio_sample(signal_with_tone.get_sample()); + squelch.process_raw_sample(raw_signal_sample); + ASSERT_TRUE(squelch.is_open()); + ASSERT_TRUE(squelch.should_process_audio()); + } + + EXPECT_GT(squelch.ctcss_count(), 0); + EXPECT_EQ(squelch.no_ctcss_count(), 0); } TEST_F(SquelchTest, wrong_ctcss) { - - float actual_tone = CTCSS::standard_tones[0]; - float expected_tone = CTCSS::standard_tones[7]; - float sample_rate = 8000; - - Squelch squelch; - squelch.set_ctcss_freq(expected_tone, sample_rate); - send_samples_for_noise_floor(squelch); - - GenerateSignal signal_with_tone(sample_rate); - signal_with_tone.add_tone(actual_tone, Tone::NORMAL); - - // send through "signal" samples until its time to process audio - for(int i = 0; i < 500 && !squelch.should_process_audio() ; ++i) { - squelch.process_raw_sample(raw_signal_sample); - } - ASSERT_TRUE(squelch.should_process_audio()); - ASSERT_FALSE(squelch.is_open()); - - // process lots of audio samples and "signal" samples and squelch never opens - for(int i = 0; i < 100000 ; ++i) { - squelch.process_audio_sample(signal_with_tone.get_sample()); - squelch.process_raw_sample(raw_signal_sample); - ASSERT_TRUE(squelch.should_process_audio()); - ASSERT_FALSE(squelch.is_open()); - } - - EXPECT_EQ(squelch.ctcss_count(), 0); - EXPECT_GT(squelch.no_ctcss_count(), 0); + float actual_tone = CTCSS::standard_tones[0]; + float expected_tone = CTCSS::standard_tones[7]; + float sample_rate = 8000; + + Squelch squelch; + squelch.set_ctcss_freq(expected_tone, sample_rate); + send_samples_for_noise_floor(squelch); + + GenerateSignal signal_with_tone(sample_rate); + signal_with_tone.add_tone(actual_tone, Tone::NORMAL); + + // send through "signal" samples until its time to process audio + for (int i = 0; i < 500 && !squelch.should_process_audio(); ++i) { + squelch.process_raw_sample(raw_signal_sample); + } + ASSERT_TRUE(squelch.should_process_audio()); + ASSERT_FALSE(squelch.is_open()); + + // process lots of audio samples and "signal" samples and squelch never opens + for (int i = 0; i < 100000; ++i) { + squelch.process_audio_sample(signal_with_tone.get_sample()); + squelch.process_raw_sample(raw_signal_sample); + ASSERT_TRUE(squelch.should_process_audio()); + ASSERT_FALSE(squelch.is_open()); + } + + EXPECT_EQ(squelch.ctcss_count(), 0); + EXPECT_GT(squelch.no_ctcss_count(), 0); } TEST_F(SquelchTest, close_ctcss) { - - float actual_tone = CTCSS::standard_tones[5]; - float expected_tone = CTCSS::standard_tones[7]; - float sample_rate = 8000; - - Squelch squelch; - squelch.set_ctcss_freq(expected_tone, sample_rate); - send_samples_for_noise_floor(squelch); - - GenerateSignal signal_with_tone(sample_rate); - signal_with_tone.add_tone(actual_tone, Tone::NORMAL); - - // send through "signal" samples until its time to process audio - for(int i = 0; i < 500 && !squelch.should_process_audio() ; ++i) { - squelch.process_raw_sample(raw_signal_sample); - } - ASSERT_TRUE(squelch.should_process_audio()); - ASSERT_FALSE(squelch.is_open()); - - // process of audio samples and "signal" samples until squelch opens - for(int i = 0; i < 500 && !squelch.is_open() ; ++i) { - squelch.process_audio_sample(signal_with_tone.get_sample()); - squelch.process_raw_sample(raw_signal_sample); - ASSERT_TRUE(squelch.should_process_audio()); - } - ASSERT_TRUE(squelch.is_open()); - - // keep processing samples until squelch closes again - for(int i = 0; i < 3000 && squelch.is_open() ; ++i) { - squelch.process_audio_sample(signal_with_tone.get_sample()); - squelch.process_raw_sample(raw_signal_sample); - ASSERT_TRUE(squelch.should_process_audio()); - } - ASSERT_FALSE(squelch.is_open()); - - // process lots of audio samples and "signal" samples and squelch stays closed - for(int i = 0; i < 100000 ; ++i) { - squelch.process_audio_sample(signal_with_tone.get_sample()); - squelch.process_raw_sample(raw_signal_sample); - ASSERT_TRUE(squelch.should_process_audio()); - ASSERT_FALSE(squelch.is_open()); - } - - EXPECT_EQ(squelch.ctcss_count(), 0); - EXPECT_GT(squelch.no_ctcss_count(), 0); + float actual_tone = CTCSS::standard_tones[5]; + float expected_tone = CTCSS::standard_tones[7]; + float sample_rate = 8000; + + Squelch squelch; + squelch.set_ctcss_freq(expected_tone, sample_rate); + send_samples_for_noise_floor(squelch); + + GenerateSignal signal_with_tone(sample_rate); + signal_with_tone.add_tone(actual_tone, Tone::NORMAL); + + // send through "signal" samples until its time to process audio + for (int i = 0; i < 500 && !squelch.should_process_audio(); ++i) { + squelch.process_raw_sample(raw_signal_sample); + } + ASSERT_TRUE(squelch.should_process_audio()); + ASSERT_FALSE(squelch.is_open()); + + // process of audio samples and "signal" samples until squelch opens + for (int i = 0; i < 500 && !squelch.is_open(); ++i) { + squelch.process_audio_sample(signal_with_tone.get_sample()); + squelch.process_raw_sample(raw_signal_sample); + ASSERT_TRUE(squelch.should_process_audio()); + } + ASSERT_TRUE(squelch.is_open()); + + // keep processing samples until squelch closes again + for (int i = 0; i < 3000 && squelch.is_open(); ++i) { + squelch.process_audio_sample(signal_with_tone.get_sample()); + squelch.process_raw_sample(raw_signal_sample); + ASSERT_TRUE(squelch.should_process_audio()); + } + ASSERT_FALSE(squelch.is_open()); + + // process lots of audio samples and "signal" samples and squelch stays closed + for (int i = 0; i < 100000; ++i) { + squelch.process_audio_sample(signal_with_tone.get_sample()); + squelch.process_raw_sample(raw_signal_sample); + ASSERT_TRUE(squelch.should_process_audio()); + ASSERT_FALSE(squelch.is_open()); + } + + EXPECT_EQ(squelch.ctcss_count(), 0); + EXPECT_GT(squelch.no_ctcss_count(), 0); } diff --git a/src/udp_stream.cpp b/src/udp_stream.cpp index a5a24d7..874bb15 100644 --- a/src/udp_stream.cpp +++ b/src/udp_stream.cpp @@ -1,90 +1,90 @@ -#include // strerror() -#include // close() -#include // LOG_INFO / LOG_ERR -#include // assert() +#include // strerror() +#include // LOG_INFO / LOG_ERR +#include // close() +#include // assert() -#include // inet_aton() -#include // getaddrinfo() +#include // inet_aton() +#include // getaddrinfo() #include "rtl_airband.h" -bool udp_stream_init(udp_stream_data *sdata, mix_modes mode, size_t len) { - // pre-allocate the stereo buffer - if (mode == MM_STEREO) { - sdata->stereo_buffer_len = len * 2; - sdata->stereo_buffer = (float *)XCALLOC(sdata->stereo_buffer_len, sizeof(float)); - } else { - sdata->stereo_buffer_len = 0; - sdata->stereo_buffer = NULL; - } +bool udp_stream_init(udp_stream_data* sdata, mix_modes mode, size_t len) { + // pre-allocate the stereo buffer + if (mode == MM_STEREO) { + sdata->stereo_buffer_len = len * 2; + sdata->stereo_buffer = (float*)XCALLOC(sdata->stereo_buffer_len, sizeof(float)); + } else { + sdata->stereo_buffer_len = 0; + sdata->stereo_buffer = NULL; + } - sdata->send_socket = -1; - sdata->dest_sockaddr_len = 0; + sdata->send_socket = -1; + sdata->dest_sockaddr_len = 0; - // lookup address / port - struct addrinfo hints, *result, *rptr; - memset(&hints, 0, sizeof(struct addrinfo)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_DGRAM; - hints.ai_flags = 0; - hints.ai_protocol = 0; - int error = getaddrinfo(sdata->dest_address, sdata->dest_port, &hints, &result); - if (error) { - log(LOG_ERR, "udp_stream: could not resolve %s:%s - %s\n", sdata->dest_address, sdata->dest_port, gai_strerror(error)); - return false; - } + // lookup address / port + struct addrinfo hints, *result, *rptr; + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = 0; + hints.ai_protocol = 0; + int error = getaddrinfo(sdata->dest_address, sdata->dest_port, &hints, &result); + if (error) { + log(LOG_ERR, "udp_stream: could not resolve %s:%s - %s\n", sdata->dest_address, sdata->dest_port, gai_strerror(error)); + return false; + } - // check each result and try to create a connection - for (rptr = result; rptr != NULL; rptr = rptr->ai_next) { - sdata->send_socket = socket(rptr->ai_family, rptr->ai_socktype, rptr->ai_protocol); - if (sdata->send_socket == -1) { - log(LOG_ERR, "udp_stream: socket failed: %s\n", strerror(errno)); - continue; - } + // check each result and try to create a connection + for (rptr = result; rptr != NULL; rptr = rptr->ai_next) { + sdata->send_socket = socket(rptr->ai_family, rptr->ai_socktype, rptr->ai_protocol); + if (sdata->send_socket == -1) { + log(LOG_ERR, "udp_stream: socket failed: %s\n", strerror(errno)); + continue; + } - if (connect(sdata->send_socket, rptr->ai_addr, rptr->ai_addrlen) == -1) { - log(LOG_INFO, "udp_stream: connect to %s:%s failed: %s\n", sdata->dest_address, sdata->dest_port, strerror(errno)); - close(sdata->send_socket); - sdata->send_socket = -1; - continue; - } + if (connect(sdata->send_socket, rptr->ai_addr, rptr->ai_addrlen) == -1) { + log(LOG_INFO, "udp_stream: connect to %s:%s failed: %s\n", sdata->dest_address, sdata->dest_port, strerror(errno)); + close(sdata->send_socket); + sdata->send_socket = -1; + continue; + } - sdata->dest_sockaddr = *rptr->ai_addr; - sdata->dest_sockaddr_len = rptr->ai_addrlen; - break; - } - freeaddrinfo(result); + sdata->dest_sockaddr = *rptr->ai_addr; + sdata->dest_sockaddr_len = rptr->ai_addrlen; + break; + } + freeaddrinfo(result); - // error if no valid socket - if (sdata->send_socket == -1) { - log(LOG_ERR, "udp_stream: could not set up UDP socket to %s:%s - all addresses failed\n", sdata->dest_address, sdata->dest_port); - return false; - } + // error if no valid socket + if (sdata->send_socket == -1) { + log(LOG_ERR, "udp_stream: could not set up UDP socket to %s:%s - all addresses failed\n", sdata->dest_address, sdata->dest_port); + return false; + } - log(LOG_INFO, "udp_stream: sending %s 32-bit float at %d Hz to %s:%s\n", mode==MM_MONO ? "Mono" : "Stereo", WAVE_RATE, sdata->dest_address, sdata->dest_port); - return true; + log(LOG_INFO, "udp_stream: sending %s 32-bit float at %d Hz to %s:%s\n", mode == MM_MONO ? "Mono" : "Stereo", WAVE_RATE, sdata->dest_address, sdata->dest_port); + return true; } -void udp_stream_write(udp_stream_data *sdata, const float *data, size_t len) { - if (sdata->send_socket != -1) { - // Send without blocking or checking for success - sendto(sdata->send_socket, data, len, MSG_DONTWAIT | MSG_NOSIGNAL, &sdata->dest_sockaddr, sdata->dest_sockaddr_len); - } +void udp_stream_write(udp_stream_data* sdata, const float* data, size_t len) { + if (sdata->send_socket != -1) { + // Send without blocking or checking for success + sendto(sdata->send_socket, data, len, MSG_DONTWAIT | MSG_NOSIGNAL, &sdata->dest_sockaddr, sdata->dest_sockaddr_len); + } } -void udp_stream_write(udp_stream_data *sdata, const float *data_left, const float *data_right, size_t len) { - if (sdata->send_socket != -1) { - assert(len * 2 <= sdata->stereo_buffer_len); - for (size_t i = 0; i < len; ++i) { - sdata->stereo_buffer[2*i] = data_left[i]; - sdata->stereo_buffer[2*i + 1] = data_right[i]; - } - udp_stream_write(sdata, sdata->stereo_buffer, len * 2); - } +void udp_stream_write(udp_stream_data* sdata, const float* data_left, const float* data_right, size_t len) { + if (sdata->send_socket != -1) { + assert(len * 2 <= sdata->stereo_buffer_len); + for (size_t i = 0; i < len; ++i) { + sdata->stereo_buffer[2 * i] = data_left[i]; + sdata->stereo_buffer[2 * i + 1] = data_right[i]; + } + udp_stream_write(sdata, sdata->stereo_buffer, len * 2); + } } -void udp_stream_shutdown(udp_stream_data *sdata) { - if (sdata->send_socket != -1) { - close(sdata->send_socket); - } +void udp_stream_shutdown(udp_stream_data* sdata) { + if (sdata->send_socket != -1) { + close(sdata->send_socket); + } } diff --git a/src/util.cpp b/src/util.cpp index b2abcd7..28f501f 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -18,148 +18,146 @@ * along with this program. If not, see . */ +#include +#include +#include // uint32_t #include -#include // uint32_t -#include -#include -#include -#include #include #include -#include -#include -#include "rtl_airband.h" +#include +#include +#include +#include #include "config.h" #include "logging.h" +#include "rtl_airband.h" - -int atomic_inc(volatile int *pv) -{ - return __sync_fetch_and_add(pv, 1); +int atomic_inc(volatile int* pv) { + return __sync_fetch_and_add(pv, 1); } -int atomic_dec(volatile int *pv) -{ - return __sync_fetch_and_sub(pv, 1); +int atomic_dec(volatile int* pv) { + return __sync_fetch_and_sub(pv, 1); } -int atomic_get(volatile int *pv) -{ - return __sync_fetch_and_add(pv, 0); +int atomic_get(volatile int* pv) { + return __sync_fetch_and_add(pv, 0); } -void tag_queue_put(device_t *dev, int freq, struct timeval tv) { - pthread_mutex_lock(&dev->tag_queue_lock); - dev->tq_head++; dev->tq_head %= TAG_QUEUE_LEN; - if(dev->tq_head == dev->tq_tail) { - log(LOG_WARNING, "tag_queue_put: queue overrun\n"); - dev->tq_tail++; - } - dev->tag_queue[dev->tq_head].freq = freq; - memcpy(&dev->tag_queue[dev->tq_head].tv, &tv, sizeof(struct timeval)); - pthread_mutex_unlock(&dev->tag_queue_lock); +void tag_queue_put(device_t* dev, int freq, struct timeval tv) { + pthread_mutex_lock(&dev->tag_queue_lock); + dev->tq_head++; + dev->tq_head %= TAG_QUEUE_LEN; + if (dev->tq_head == dev->tq_tail) { + log(LOG_WARNING, "tag_queue_put: queue overrun\n"); + dev->tq_tail++; + } + dev->tag_queue[dev->tq_head].freq = freq; + memcpy(&dev->tag_queue[dev->tq_head].tv, &tv, sizeof(struct timeval)); + pthread_mutex_unlock(&dev->tag_queue_lock); } -void tag_queue_get(device_t *dev, struct freq_tag *tag) { - int i; - - if(!tag) return; - pthread_mutex_lock(&dev->tag_queue_lock); - if(dev->tq_head == dev->tq_tail) { /* empty queue */ - tag->freq = -1; - } else { -// read queue entry at pos tq_tail+1 without dequeueing it - i = dev->tq_tail+1; i %= TAG_QUEUE_LEN; - tag->freq = dev->tag_queue[i].freq; - memcpy(&tag->tv, &dev->tag_queue[i].tv, sizeof(struct timeval)); - } - pthread_mutex_unlock(&dev->tag_queue_lock); +void tag_queue_get(device_t* dev, struct freq_tag* tag) { + int i; + + if (!tag) + return; + pthread_mutex_lock(&dev->tag_queue_lock); + if (dev->tq_head == dev->tq_tail) { /* empty queue */ + tag->freq = -1; + } else { + // read queue entry at pos tq_tail+1 without dequeueing it + i = dev->tq_tail + 1; + i %= TAG_QUEUE_LEN; + tag->freq = dev->tag_queue[i].freq; + memcpy(&tag->tv, &dev->tag_queue[i].tv, sizeof(struct timeval)); + } + pthread_mutex_unlock(&dev->tag_queue_lock); } -void tag_queue_advance(device_t *dev) { - pthread_mutex_lock(&dev->tag_queue_lock); - dev->tq_tail++; dev->tq_tail %= TAG_QUEUE_LEN; - pthread_mutex_unlock(&dev->tag_queue_lock); +void tag_queue_advance(device_t* dev) { + pthread_mutex_lock(&dev->tag_queue_lock); + dev->tq_tail++; + dev->tq_tail %= TAG_QUEUE_LEN; + pthread_mutex_unlock(&dev->tag_queue_lock); } -void *xcalloc(size_t nmemb, size_t size, const char *file, const int line, const char *func) { - void *ptr = calloc(nmemb, size); - if(ptr == NULL) { - log(LOG_ERR, "%s:%d: %s(): calloc(%zu, %zu) failed: %s\n", - file, line, func, nmemb, size, strerror(errno)); - error(); - } - return ptr; +void* xcalloc(size_t nmemb, size_t size, const char* file, const int line, const char* func) { + void* ptr = calloc(nmemb, size); + if (ptr == NULL) { + log(LOG_ERR, "%s:%d: %s(): calloc(%zu, %zu) failed: %s\n", file, line, func, nmemb, size, strerror(errno)); + error(); + } + return ptr; } -void *xrealloc(void *ptr, size_t size, const char *file, const int line, const char *func) { - ptr = realloc(ptr, size); - if(ptr == NULL) { - log(LOG_ERR, "%s:%d: %s(): realloc(%zu) failed: %s\n", - file, line, func, size, strerror(errno)); - error(); - } - return ptr; +void* xrealloc(void* ptr, size_t size, const char* file, const int line, const char* func) { + ptr = realloc(ptr, size); + if (ptr == NULL) { + log(LOG_ERR, "%s:%d: %s(): realloc(%zu) failed: %s\n", file, line, func, size, strerror(errno)); + error(); + } + return ptr; } static float sin_lut[257], cos_lut[257]; void sincosf_lut_init() { - for(uint32_t i = 0; i < 256; i++) - SINCOSF(2.0F * M_PI * (float)i / 256.0f, sin_lut + i, cos_lut + i); - sin_lut[256] = sin_lut[0]; - cos_lut[256] = cos_lut[0]; + for (uint32_t i = 0; i < 256; i++) + SINCOSF(2.0F * M_PI * (float)i / 256.0f, sin_lut + i, cos_lut + i); + sin_lut[256] = sin_lut[0]; + cos_lut[256] = cos_lut[0]; } // phi range must be (0..1), rescaled to 0x0-0xFFFFFF -void sincosf_lut(uint32_t phi, float *sine, float *cosine) { - float v1, v2, fract; - uint32_t idx; -// get LUT index - idx = phi >> 16; -// cast fixed point fraction to float - fract = (float)(phi & 0xffff) / 65536.0f; -// get two adjacent values from LUT and interpolate - v1 = sin_lut[idx]; - v2 = sin_lut[idx+1]; - *sine = v1 + (v2 - v1) * fract; - v1 = cos_lut[idx]; - v2 = cos_lut[idx+1]; - *cosine = v1 + (v2 - v1) * fract; +void sincosf_lut(uint32_t phi, float* sine, float* cosine) { + float v1, v2, fract; + uint32_t idx; + // get LUT index + idx = phi >> 16; + // cast fixed point fraction to float + fract = (float)(phi & 0xffff) / 65536.0f; + // get two adjacent values from LUT and interpolate + v1 = sin_lut[idx]; + v2 = sin_lut[idx + 1]; + *sine = v1 + (v2 - v1) * fract; + v1 = cos_lut[idx]; + v2 = cos_lut[idx + 1]; + *cosine = v1 + (v2 - v1) * fract; } /* librtlsdr-keenerd, (c) Kyle Keen */ -double atofs(char *s) { - char last; - int len; - double suff = 1.0; - len = strlen(s); - last = s[len-1]; - s[len-1] = '\0'; - switch (last) { - case 'g': - case 'G': - suff *= 1e3; - [[fallthrough]]; - case 'm': - case 'M': - suff *= 1e3; - [[fallthrough]]; - case 'k': - case 'K': - suff *= 1e3; - suff *= atof(s); - s[len-1] = last; - return suff; - } - s[len-1] = last; - return atof(s); +double atofs(char* s) { + char last; + int len; + double suff = 1.0; + len = strlen(s); + last = s[len - 1]; + s[len - 1] = '\0'; + switch (last) { + case 'g': + case 'G': + suff *= 1e3; + [[fallthrough]]; + case 'm': + case 'M': + suff *= 1e3; + [[fallthrough]]; + case 'k': + case 'K': + suff *= 1e3; + suff *= atof(s); + s[len - 1] = last; + return suff; + } + s[len - 1] = last; + return atof(s); } -double delta_sec(const timeval *start, const timeval *stop) { - timeval delta; - timersub(stop, start, &delta); - return delta.tv_sec + delta.tv_usec/1000000.0; +double delta_sec(const timeval* start, const timeval* stop) { + timeval delta; + timersub(stop, start, &delta); + return delta.tv_sec + delta.tv_usec / 1000000.0; } // level to/from dBFS conversion assumes level is nomalized to 1 and is based on: @@ -168,17 +166,17 @@ double delta_sec(const timeval *start, const timeval *stop) { // expanded form: // 20.0f * log10f(level / fft_size) + 7.54f + 10.0f * log10f(fft_size/2) - 2.38f -const float &dBFS_offset(void) { - static const float offset = 7.54f + 10.0f * log10f(fft_size/2) - 2.38f; - return offset; +const float& dBFS_offset(void) { + static const float offset = 7.54f + 10.0f * log10f(fft_size / 2) - 2.38f; + return offset; } -float dBFS_to_level(const float &dBFS) { - return pow(10.0, (dBFS - dBFS_offset()) / 20.0f) * fft_size; +float dBFS_to_level(const float& dBFS) { + return pow(10.0, (dBFS - dBFS_offset()) / 20.0f) * fft_size; } -float level_to_dBFS(const float &level) { - return std::min(0.0f, 20.0f * log10f(level / fft_size) + dBFS_offset()); +float level_to_dBFS(const float& level) { + return std::min(0.0f, 20.0f * log10f(level / fft_size) + dBFS_offset()); } // vim: ts=4 From a0802b7853f80808b9e94c34d28f98d0fd246194 Mon Sep 17 00:00:00 2001 From: charlie-foxtrot <13514783+charlie-foxtrot@users.noreply.github.com> Date: Tue, 6 Feb 2024 22:22:00 -0800 Subject: [PATCH 6/9] cleanup and add pre-commit hook --- .../{Dockerfile.ubuntu-latest => Dockerfile} | 5 ++++- .devcontainer/Dockerfile.ubuntu-20.04 | 20 ------------------- .devcontainer/devcontainer.json | 4 ++-- .devcontainer/{shell.ubuntu-20.04 => shell} | 4 ++-- .devcontainer/shell.ubuntu-latest | 10 ---------- .pre-commit-config.yaml | 14 +++++++++++++ .vscode/settings.json | 7 +++++++ src/input-file.cpp | 2 -- src/input-file.h | 2 -- src/input-helpers.cpp | 2 -- src/mixer.cpp | 2 -- src/output.cpp | 2 -- src/rtl_airband.cpp | 2 -- src/rtl_airband.h | 2 -- src/util.cpp | 2 -- 15 files changed, 29 insertions(+), 51 deletions(-) rename .devcontainer/{Dockerfile.ubuntu-latest => Dockerfile} (87%) delete mode 100644 .devcontainer/Dockerfile.ubuntu-20.04 rename .devcontainer/{shell.ubuntu-20.04 => shell} (59%) delete mode 100755 .devcontainer/shell.ubuntu-latest create mode 100644 .pre-commit-config.yaml create mode 100644 .vscode/settings.json diff --git a/.devcontainer/Dockerfile.ubuntu-latest b/.devcontainer/Dockerfile similarity index 87% rename from .devcontainer/Dockerfile.ubuntu-latest rename to .devcontainer/Dockerfile index 5515542..b26ce84 100644 --- a/.devcontainer/Dockerfile.ubuntu-latest +++ b/.devcontainer/Dockerfile @@ -11,10 +11,13 @@ RUN DEBIAN_FRONTEND=noninteractive \ git \ sudo \ gdb \ - clang-format-14 + clang-format-14 \ + python3-pip WORKDIR /app COPY .github/install_dependencies /app/ RUN /app/install_dependencies +RUN pip install pre-commit + RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* diff --git a/.devcontainer/Dockerfile.ubuntu-20.04 b/.devcontainer/Dockerfile.ubuntu-20.04 deleted file mode 100644 index a0bcff7..0000000 --- a/.devcontainer/Dockerfile.ubuntu-20.04 +++ /dev/null @@ -1,20 +0,0 @@ -FROM ubuntu:20.04 - -RUN sed -i 's/^# \(.*export LS_OPTIONS.*$\)/\1/g' ~/.bashrc && \ - sed -i 's/^# \(.*alias ll.*$\)/\1/g' ~/.bashrc - -RUN ln -fs /usr/share/zoneinfo/America/Los_Angeles /etc/localtime -RUN DEBIAN_FRONTEND=noninteractive \ - apt-get update && \ - apt-get install -y \ - tzdata\ - git \ - sudo \ - gdb \ - clang-format-14 - -WORKDIR /app -COPY .github/install_dependencies /app/ -RUN /app/install_dependencies - -RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 5faa346..44d9b65 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -6,11 +6,11 @@ // Sets the run context to one level up instead of the .devcontainer folder. "context": "..", - "dockerFile": "Dockerfile.ubuntu-latest", + "dockerFile": "Dockerfile", "updateContentCommand" : "apt-get install git", - "postCreateCommand" : "cmake -B /app/build -DCMAKE_BUILD_TYPE=Debug -DNFM=TRUE -DBUILD_UNITTESTS=true", + "postCreateCommand" : "cmake -B /app/build -DCMAKE_BUILD_TYPE=Debug -DNFM=TRUE -DBUILD_UNITTESTS=true ; pre-commit install", // vs code extensions to install in the dev container "customizations": { diff --git a/.devcontainer/shell.ubuntu-20.04 b/.devcontainer/shell similarity index 59% rename from .devcontainer/shell.ubuntu-20.04 rename to .devcontainer/shell index dd3bc05..d4992c8 100755 --- a/.devcontainer/shell.ubuntu-20.04 +++ b/.devcontainer/shell @@ -3,8 +3,8 @@ cd `dirname $0`/../ # build container -docker build -t rtl_airband.ubuntu-20.04 -f .devcontainer/Dockerfile.ubuntu-20.04 . +docker build -t rtl_airband-dev -f .devcontainer/Dockerfile . # run bash in container -docker run --rm -v $(pwd):/app/ -it --entrypoint bash rtl_airband.ubuntu-20.04 +docker run --rm -v $(pwd):/app/ -it --entrypoint bash rtl_airband-dev diff --git a/.devcontainer/shell.ubuntu-latest b/.devcontainer/shell.ubuntu-latest deleted file mode 100755 index 82b5dd2..0000000 --- a/.devcontainer/shell.ubuntu-latest +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -e - -cd `dirname $0`/../ - -# build container -docker build -t rtl_airband.ubuntu-latest -f .devcontainer/Dockerfile.ubuntu-latest . - -# run bash in container -docker run --rm -v $(pwd):/app/ -it --entrypoint bash rtl_airband.ubuntu-latest - diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..50928be --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,14 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + - id: check-shebang-scripts-are-executable + +- repo: https://github.com/pre-commit/mirrors-clang-format + rev: v14.0.6 + hooks: + - id: clang-format + files: src/.*\.cpp|src/.*\.h diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..3b27eb1 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "editor.formatOnPaste": true, + "editor.formatOnSave": true, + "editor.formatOnType": true, + "editor.defaultFormatter": "xaver.clang-format", + "clang-format.executable": "clang-format-14" +} diff --git a/src/input-file.cpp b/src/input-file.cpp index 9277b56..c7fc150 100644 --- a/src/input-file.cpp +++ b/src/input-file.cpp @@ -179,5 +179,3 @@ MODULE_EXPORT input_t* file_input_new() { return input; } - -// vim: ts=4 diff --git a/src/input-file.h b/src/input-file.h index 3d0b033..e7b321a 100644 --- a/src/input-file.h +++ b/src/input-file.h @@ -27,5 +27,3 @@ typedef struct { FILE* input_file; float speedup_factor; } file_dev_data_t; - -// vim: ts=4 diff --git a/src/input-helpers.cpp b/src/input-helpers.cpp index dda3eb7..c6ffcd9 100644 --- a/src/input-helpers.cpp +++ b/src/input-helpers.cpp @@ -61,5 +61,3 @@ void circbuffer_append(input_t* const input, unsigned char* buf, size_t len) { } pthread_mutex_unlock(&input->buffer_lock); } - -// vim: ts=4 diff --git a/src/mixer.cpp b/src/mixer.cpp index 817ff02..13a584a 100644 --- a/src/mixer.cpp +++ b/src/mixer.cpp @@ -259,5 +259,3 @@ void* mixer_thread(void* param) { } return 0; } - -// vim: ts=4 diff --git a/src/output.cpp b/src/output.cpp index c97a8dd..f90a0d2 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -1003,5 +1003,3 @@ void* output_check_thread(void*) { } return 0; } - -// vim: ts=4 diff --git a/src/rtl_airband.cpp b/src/rtl_airband.cpp index 0cfb8e3..baf1594 100644 --- a/src/rtl_airband.cpp +++ b/src/rtl_airband.cpp @@ -1162,5 +1162,3 @@ int main(int argc, char* argv[]) { #endif /* WITH_PROFILING */ return 0; } - -// vim: ts=4 diff --git a/src/rtl_airband.h b/src/rtl_airband.h index c706698..7c393ec 100644 --- a/src/rtl_airband.h +++ b/src/rtl_airband.h @@ -399,5 +399,3 @@ void pulse_write_stream(pulse_data* pdata, mix_modes mode, const float* data_lef #endif /* WITH_PULSEAUDIO */ #endif /* _RTL_AIRBAND_H */ - -// vim: ts=4 diff --git a/src/util.cpp b/src/util.cpp index 28f501f..2da0157 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -178,5 +178,3 @@ float dBFS_to_level(const float& dBFS) { float level_to_dBFS(const float& level) { return std::min(0.0f, 20.0f * log10f(level / fft_size) + dBFS_offset()); } - -// vim: ts=4 From 3e731488cfb5060748c49ef028ff3f1532952772 Mon Sep 17 00:00:00 2001 From: charlie-foxtrot <13514783+charlie-foxtrot@users.noreply.github.com> Date: Tue, 6 Feb 2024 22:49:49 -0800 Subject: [PATCH 7/9] run precommit checks on all files --- .clang-format | 2 +- .devcontainer/shell | 1 - .github/ISSUE_TEMPLATE/bug_report.md | 2 +- .github/workflows/build_docker_containers.yml | 3 +- .github/workflows/ci_build.yml | 2 +- .github/workflows/code_formatting.yml | 2 +- .github/workflows/platform_build.yml | 2 +- .github/workflows/version_bump.yml | 2 +- .vscode/c_cpp_properties.json | 2 +- .vscode/launch.json | 2 +- Dockerfile | 4 +- NEWS.md | 2 +- README.md | 1 - config/mixers.conf | 2 +- init.d/rtl_airband-gentoo.sh | 1 - src/CMakeLists.txt | 3 +- src/CMakeModules/FindMiriSDR.cmake | 1 - src/CMakeModules/FindRTLSDR.cmake | 1 - src/hello_fft/gpu_fft.h | 61 ++++++++----------- src/hello_fft/gpu_fft_trans.h | 13 ++-- src/hello_fft/mailbox.h | 6 +- src/rtl_airband_neon.s | 18 +++--- src/test_base_class.cpp | 2 +- 23 files changed, 56 insertions(+), 79 deletions(-) diff --git a/.clang-format b/.clang-format index 90fb419..1795fbc 100644 --- a/.clang-format +++ b/.clang-format @@ -2,4 +2,4 @@ BasedOnStyle: Chromium IndentWidth: 4 ObjCBlockIndentWidth: 4 -ColumnLimit: 200 \ No newline at end of file +ColumnLimit: 200 diff --git a/.devcontainer/shell b/.devcontainer/shell index d4992c8..63b78e1 100755 --- a/.devcontainer/shell +++ b/.devcontainer/shell @@ -7,4 +7,3 @@ docker build -t rtl_airband-dev -f .devcontainer/Dockerfile . # run bash in container docker run --rm -v $(pwd):/app/ -it --entrypoint bash rtl_airband-dev - diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 5f565ac..a415b97 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -16,7 +16,7 @@ Please use this template to create your bug report. By providing as much info as - RTLSDR-Airband version you are using (stable release number or branch/commit): - `make` options used to build the program: - Hardware platform (eg. x86_64, Raspberry Pi v4): - - Operating system name and version: + - Operating system name and version: **What happened?** diff --git a/.github/workflows/build_docker_containers.yml b/.github/workflows/build_docker_containers.yml index 8afd930..9a796ea 100644 --- a/.github/workflows/build_docker_containers.yml +++ b/.github/workflows/build_docker_containers.yml @@ -7,7 +7,7 @@ on: pull_request: workflow_dispatch: schedule: - - cron: '29 13 * * *' # run daily + - cron: '29 13 * * *' # run daily jobs: build_docker_containers: @@ -51,4 +51,3 @@ jobs: push: true tags: ${{ steps.metadata.outputs.tags }} labels: ${{ steps.metadata.outputs.labels }} - \ No newline at end of file diff --git a/.github/workflows/ci_build.yml b/.github/workflows/ci_build.yml index 503c241..8158876 100644 --- a/.github/workflows/ci_build.yml +++ b/.github/workflows/ci_build.yml @@ -7,7 +7,7 @@ on: pull_request: workflow_dispatch: schedule: - - cron: '39 13 * * *' # run daily + - cron: '39 13 * * *' # run daily jobs: ci_build: diff --git a/.github/workflows/code_formatting.yml b/.github/workflows/code_formatting.yml index 861e6e1..0c8d699 100644 --- a/.github/workflows/code_formatting.yml +++ b/.github/workflows/code_formatting.yml @@ -3,7 +3,7 @@ name: Code Formatting on: pull_request: schedule: - - cron: '39 13 * * *' # run daily + - cron: '39 13 * * *' # run daily jobs: code_formatting: diff --git a/.github/workflows/platform_build.yml b/.github/workflows/platform_build.yml index f3cac31..03be764 100644 --- a/.github/workflows/platform_build.yml +++ b/.github/workflows/platform_build.yml @@ -7,7 +7,7 @@ on: pull_request: workflow_dispatch: schedule: - - cron: '39 13 * * *' # run daily + - cron: '39 13 * * *' # run daily jobs: platform_build: diff --git a/.github/workflows/version_bump.yml b/.github/workflows/version_bump.yml index 799b2cd..6c65afc 100644 --- a/.github/workflows/version_bump.yml +++ b/.github/workflows/version_bump.yml @@ -36,7 +36,7 @@ jobs: --repo="$GITHUB_REPOSITORY" \ --title="Version ${tag#v}" \ --generate-notes - + - name: Run CI on ${{ steps.tag.outputs.new_tag }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 819dadd..ec98d1a 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -16,4 +16,4 @@ } ], "version": 4 -} \ No newline at end of file +} diff --git a/.vscode/launch.json b/.vscode/launch.json index 7d7ba7d..008efb1 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -30,4 +30,4 @@ } ] -} \ No newline at end of file +} diff --git a/Dockerfile b/Dockerfile index 9283c3f..f39cd71 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,10 +22,10 @@ RUN apt-get update && \ && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* - + # set working dir for compiling dependencies WORKDIR /build_dependencies - + # compile / install rtl-sdr-blog version of rtl-sdr for v4 support RUN git clone https://github.com/rtlsdrblog/rtl-sdr-blog && \ cd rtl-sdr-blog/ && \ diff --git a/NEWS.md b/NEWS.md index ae74495..e569721 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,7 +6,7 @@ Version 5.0.0 (Jan 21, 2024): * NOTE: Going forward a release tag will be automatically created on each merge to `main`, and changes will not be reflected in this file. For changes between versions see the repo's [release history](https://github.com/charlie-foxtrot/RTLSDR-Airband/releases). * NOTE: Going forward PRs will be opened directly against `main` and the `unstable` branch will no longer be used. -* NOTE: This repo has significantly diverged from the original project [microtony/RTLSDR-Airband](https://github.com/microtony/RTLSDR-Airband) so it has been been detached (ie no longer a fork). +* NOTE: This repo has significantly diverged from the original project [microtony/RTLSDR-Airband](https://github.com/microtony/RTLSDR-Airband) so it has been been detached (ie no longer a fork). * Changes in this release, see [#444](https://github.com/charlie-foxtrot/RTLSDR-Airband/pull/444): * build and publish docker containers diff --git a/README.md b/README.md index d63fc3d..2f57462 100644 --- a/README.md +++ b/README.md @@ -95,4 +95,3 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * Copyright (C) 2012 by Steve Markgraf * Copyright (C) 2015 by Kyle Keen * GNU General Public License Version 2 - diff --git a/config/mixers.conf b/config/mixers.conf index 789fa7c..6ca7b5e 100644 --- a/config/mixers.conf +++ b/config/mixers.conf @@ -39,7 +39,7 @@ mixers: { }, mixer2: { outputs: ( - { + { type = "icecast"; server = "icecast.server.example.org"; port = 8080; diff --git a/init.d/rtl_airband-gentoo.sh b/init.d/rtl_airband-gentoo.sh index 7967590..c00f848 100755 --- a/init.d/rtl_airband-gentoo.sh +++ b/init.d/rtl_airband-gentoo.sh @@ -38,4 +38,3 @@ stop() { --pidfile "${RTLAIRBAND_PIDFILE}" --quiet eend $? } - diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4fcf473..8b37998 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -354,7 +354,7 @@ if(BUILD_UNITTESTS) enable_testing() file(GLOB_RECURSE TEST_FILES "test_*.cpp") - list(APPEND TEST_FILES + list(APPEND TEST_FILES squelch.cpp logging.cpp filters.cpp @@ -383,4 +383,3 @@ if(BUILD_UNITTESTS) gtest_discover_tests(unittests) endif() - diff --git a/src/CMakeModules/FindMiriSDR.cmake b/src/CMakeModules/FindMiriSDR.cmake index 22a6898..af36307 100644 --- a/src/CMakeModules/FindMiriSDR.cmake +++ b/src/CMakeModules/FindMiriSDR.cmake @@ -33,4 +33,3 @@ if(NOT MIRISDR_FOUND) mark_as_advanced(MIRISDR_INCLUDE_DIR MIRISDR_LIBRARY) endif(NOT MIRISDR_FOUND) - diff --git a/src/CMakeModules/FindRTLSDR.cmake b/src/CMakeModules/FindRTLSDR.cmake index 801d9e0..70ebc85 100644 --- a/src/CMakeModules/FindRTLSDR.cmake +++ b/src/CMakeModules/FindRTLSDR.cmake @@ -55,4 +55,3 @@ if(NOT RTLSDR_FOUND) mark_as_advanced(RTLSDR_INCLUDE_DIR RTLSDR_LIBRARY) endif(NOT RTLSDR_FOUND) - diff --git a/src/hello_fft/gpu_fft.h b/src/hello_fft/gpu_fft.h index 19cc14f..e9599f4 100644 --- a/src/hello_fft/gpu_fft.h +++ b/src/hello_fft/gpu_fft.h @@ -33,8 +33,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define GPU_FFT_PI 3.14159265358979323846 -#define GPU_FFT_FWD 0 // forward FFT -#define GPU_FFT_REV 1 // inverse FFT +#define GPU_FFT_FWD 0 // forward FFT +#define GPU_FFT_REV 1 // inverse FFT struct GPU_FFT_COMPLEX { float re, im; @@ -42,17 +42,19 @@ struct GPU_FFT_COMPLEX { struct GPU_FFT_PTR { unsigned vc; - union { struct GPU_FFT_COMPLEX *cptr; - void *vptr; - char *bptr; - float *fptr; - unsigned *uptr; } arm; + union { + struct GPU_FFT_COMPLEX* cptr; + void* vptr; + char* bptr; + float* fptr; + unsigned* uptr; + } arm; }; struct GPU_FFT_BASE { int mb; unsigned handle, size, vc_msg, vc_code, vc_unifs[GPU_FFT_QPUS]; - volatile unsigned *peri; + volatile unsigned* peri; }; struct GPU_FFT { @@ -61,41 +63,30 @@ struct GPU_FFT { int x, y, step; }; -int gpu_fft_prepare( - int mb, // mailbox file_desc - int log2_N, // log2(FFT_length) = 8...20 - int direction, // GPU_FFT_FWD: fft(); GPU_FFT_REV: ifft() - int jobs, // number of transforms in batch - struct GPU_FFT **fft); +int gpu_fft_prepare(int mb, // mailbox file_desc + int log2_N, // log2(FFT_length) = 8...20 + int direction, // GPU_FFT_FWD: fft(); GPU_FFT_REV: ifft() + int jobs, // number of transforms in batch + struct GPU_FFT** fft); -unsigned gpu_fft_execute( - struct GPU_FFT *info); +unsigned gpu_fft_execute(struct GPU_FFT* info); -void gpu_fft_release( - struct GPU_FFT *info); +void gpu_fft_release(struct GPU_FFT* info); // private -int gpu_fft_twiddle_size(int, int *, int *, int *); -void gpu_fft_twiddle_data(int, int, float *); -unsigned int gpu_fft_shader_size(int); -unsigned int *gpu_fft_shader_code(int); +int gpu_fft_twiddle_size(int, int*, int*, int*); +void gpu_fft_twiddle_data(int, int, float*); +unsigned int gpu_fft_shader_size(int); +unsigned int* gpu_fft_shader_code(int); // gpu_fft_base: -unsigned gpu_fft_base_exec ( - struct GPU_FFT_BASE *base, - unsigned num_qpus); +unsigned gpu_fft_base_exec(struct GPU_FFT_BASE* base, unsigned num_qpus); -int gpu_fft_alloc ( - int mb, - unsigned size, - struct GPU_FFT_PTR *ptr); +int gpu_fft_alloc(int mb, unsigned size, struct GPU_FFT_PTR* ptr); -void gpu_fft_base_release( - struct GPU_FFT_BASE *base); +void gpu_fft_base_release(struct GPU_FFT_BASE* base); -unsigned gpu_fft_ptr_inc ( - struct GPU_FFT_PTR *ptr, - int bytes); +unsigned gpu_fft_ptr_inc(struct GPU_FFT_PTR* ptr, int bytes); -#endif // __GPU_FFT__ +#endif // __GPU_FFT__ diff --git a/src/hello_fft/gpu_fft_trans.h b/src/hello_fft/gpu_fft_trans.h index 682efc1..9fc1abd 100644 --- a/src/hello_fft/gpu_fft_trans.h +++ b/src/hello_fft/gpu_fft_trans.h @@ -32,14 +32,9 @@ struct GPU_FFT_TRANS { struct GPU_FFT_BASE base; }; -int gpu_fft_trans_prepare( - int mb, - struct GPU_FFT *src, - struct GPU_FFT *dst, - struct GPU_FFT_TRANS **out); +int gpu_fft_trans_prepare(int mb, struct GPU_FFT* src, struct GPU_FFT* dst, struct GPU_FFT_TRANS** out); -unsigned gpu_fft_trans_execute( // src->out ==> T ==> dst->in - struct GPU_FFT_TRANS *info); +unsigned gpu_fft_trans_execute( // src->out ==> T ==> dst->in + struct GPU_FFT_TRANS* info); -void gpu_fft_trans_release( - struct GPU_FFT_TRANS *info); +void gpu_fft_trans_release(struct GPU_FFT_TRANS* info); diff --git a/src/hello_fft/mailbox.h b/src/hello_fft/mailbox.h index 370d115..7a4ef41 100644 --- a/src/hello_fft/mailbox.h +++ b/src/hello_fft/mailbox.h @@ -28,7 +28,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #define MAJOR_NUM 100 -#define IOCTL_MBOX_PROPERTY _IOWR(MAJOR_NUM, 0, char *) +#define IOCTL_MBOX_PROPERTY _IOWR(MAJOR_NUM, 0, char*) #define DEVICE_FILE_NAME "/dev/vcio" int mbox_open(); @@ -39,8 +39,8 @@ unsigned mem_alloc(int file_desc, unsigned size, unsigned align, unsigned flags) unsigned mem_free(int file_desc, unsigned handle); unsigned mem_lock(int file_desc, unsigned handle); unsigned mem_unlock(int file_desc, unsigned handle); -void *mapmem(unsigned base, unsigned size); -void unmapmem(void *addr, unsigned size); +void* mapmem(unsigned base, unsigned size); +void unmapmem(void* addr, unsigned size); unsigned execute_code(int file_desc, unsigned code, unsigned r0, unsigned r1, unsigned r2, unsigned r3, unsigned r4, unsigned r5); unsigned execute_qpu(int file_desc, unsigned num_qpus, unsigned control, unsigned noflush, unsigned timeout); diff --git a/src/rtl_airband_neon.s b/src/rtl_airband_neon.s index 6689c22..aa249c8 100644 --- a/src/rtl_airband_neon.s +++ b/src/rtl_airband_neon.s @@ -1,23 +1,23 @@ -# +# # RTLSDR AM demodulator and streaming -# +# # Copyright (c) 2014 Wong Man Hang # # Updates for NEON coprocessor by Tomasz Lemiech -# +# # 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 . -# +# .text .align 2 @@ -26,7 +26,7 @@ .fpu neon samplefft: - + push {r4-r12, lr} vpush {d4-d15} @@ -46,7 +46,7 @@ ldrb r11, [r1, #6] ldrb r12, [r1, #7] .a: - + ldr r5, [r3, r5, LSL #2] ldr r6, [r3, r6, LSL #2] ldr r7, [r3, r7, LSL #2] @@ -81,5 +81,3 @@ bne .a vpop {d4-d15} pop {r4-r12, pc} - - diff --git a/src/test_base_class.cpp b/src/test_base_class.cpp index 8931e8d..1882d75 100644 --- a/src/test_base_class.cpp +++ b/src/test_base_class.cpp @@ -135,4 +135,4 @@ TEST(TestHelpers, delete_directory) { // root temp dir should no longer exist ASSERT_NE(stat(temp_dir.c_str(), &info), 0); -} \ No newline at end of file +} From 2e79840488a26c47c7387f37a703064c443d8236 Mon Sep 17 00:00:00 2001 From: charlie-foxtrot <13514783+charlie-foxtrot@users.noreply.github.com> Date: Tue, 6 Feb 2024 22:53:43 -0800 Subject: [PATCH 8/9] reformat all code --- scripts/reformat_code | 2 +- src/hello_fft/gpu_fft.c | 80 ++++---- src/hello_fft/gpu_fft_base.c | 84 ++++---- src/hello_fft/gpu_fft_shaders.c | 56 ++--- src/hello_fft/gpu_fft_twiddles.c | 258 +++++++++++------------ src/hello_fft/mailbox.c | 339 +++++++++++++++---------------- 6 files changed, 383 insertions(+), 436 deletions(-) diff --git a/scripts/reformat_code b/scripts/reformat_code index 3687fd3..8c74bcc 100755 --- a/scripts/reformat_code +++ b/scripts/reformat_code @@ -1,3 +1,3 @@ #!/bin/bash -find src/*.h src/*.cpp | xargs clang-format-14 -i +find src/*.h src/*.cpp src/hello_fft/*.h src/hello_fft/*.c | xargs clang-format-14 -i diff --git a/src/hello_fft/gpu_fft.c b/src/hello_fft/gpu_fft.c index e2053d8..98dcff6 100644 --- a/src/hello_fft/gpu_fft.c +++ b/src/hello_fft/gpu_fft.c @@ -30,58 +30,59 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "gpu_fft.h" -#define GPU_FFT_BUSY_WAIT_LIMIT (5<<12) // ~1ms +#define GPU_FFT_BUSY_WAIT_LIMIT (5 << 12) // ~1ms typedef struct GPU_FFT_COMPLEX COMPLEX; -int gpu_fft_prepare( - int mb, // mailbox file_desc - int log2_N, // log2(FFT_length) = 8...20 - int direction, // GPU_FFT_FWD: fft(); GPU_FFT_REV: ifft() - int jobs, // number of transforms in batch - struct GPU_FFT **fft) { - +int gpu_fft_prepare(int mb, // mailbox file_desc + int log2_N, // log2(FFT_length) = 8...20 + int direction, // GPU_FFT_FWD: fft(); GPU_FFT_REV: ifft() + int jobs, // number of transforms in batch + struct GPU_FFT** fft) { unsigned info_bytes, twid_bytes, data_bytes, code_bytes, unif_bytes, mail_bytes; unsigned size, *uptr, vc_tw, vc_data; int i, q, shared, unique, passes, ret; - struct GPU_FFT_BASE *base; + struct GPU_FFT_BASE* base; struct GPU_FFT_PTR ptr; - struct GPU_FFT *info; + struct GPU_FFT* info; - if (gpu_fft_twiddle_size(log2_N, &shared, &unique, &passes)) return -2; + if (gpu_fft_twiddle_size(log2_N, &shared, &unique, &passes)) + return -2; info_bytes = 4096; - data_bytes = (1+((sizeof(COMPLEX)<x = 1<x = 1 << log2_N; info->y = jobs; // Ping-pong buffers leave results in or out of place info->in = info->out = ptr.arm.cptr; info->step = data_bytes / sizeof(COMPLEX); - if (passes&1) info->out += info->step * jobs; // odd => out of place - vc_data = gpu_fft_ptr_inc(&ptr, data_bytes*jobs*2); + if (passes & 1) + info->out += info->step * jobs; // odd => out of place + vc_data = gpu_fft_ptr_inc(&ptr, data_bytes * jobs * 2); // Shader code memcpy(ptr.arm.vptr, gpu_fft_shader_code(log2_N), code_bytes); @@ -94,27 +95,26 @@ int gpu_fft_prepare( uptr = ptr.arm.uptr; // Uniforms - for (q=0; qvc_unifs[q] = gpu_fft_ptr_inc(&ptr, sizeof(int)*(5+jobs*2)); + base->vc_unifs[q] = gpu_fft_ptr_inc(&ptr, sizeof(int) * (5 + jobs * 2)); } - if ((jobs<vc_msg = 0; - } - else { + } else { // Mailbox message - for (q=0; qvc_unifs[q]; *uptr++ = base->vc_code; } @@ -126,10 +126,10 @@ int gpu_fft_prepare( return 0; } -unsigned gpu_fft_execute(struct GPU_FFT *info) { +unsigned gpu_fft_execute(struct GPU_FFT* info) { return gpu_fft_base_exec(&info->base, GPU_FFT_QPUS); } -void gpu_fft_release(struct GPU_FFT *info) { +void gpu_fft_release(struct GPU_FFT* info) { gpu_fft_base_release(&info->base); } diff --git a/src/hello_fft/gpu_fft_base.c b/src/hello_fft/gpu_fft_base.c index ba92393..6c2e211 100644 --- a/src/hello_fft/gpu_fft_base.c +++ b/src/hello_fft/gpu_fft_base.c @@ -30,76 +30,67 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "gpu_fft.h" #include "mailbox.h" -#define BUS_TO_PHYS(x) ((x)&~0xC0000000) +#define BUS_TO_PHYS(x) ((x) & ~0xC0000000) // V3D spec: http://www.broadcom.com/docs/support/videocore/VideoCoreIV-AG100-R.pdf -#define V3D_L2CACTL (0xC00020>>2) -#define V3D_SLCACTL (0xC00024>>2) -#define V3D_SRQPC (0xC00430>>2) -#define V3D_SRQUA (0xC00434>>2) -#define V3D_SRQCS (0xC0043c>>2) -#define V3D_DBCFG (0xC00e00>>2) -#define V3D_DBQITE (0xC00e2c>>2) -#define V3D_DBQITC (0xC00e30>>2) +#define V3D_L2CACTL (0xC00020 >> 2) +#define V3D_SLCACTL (0xC00024 >> 2) +#define V3D_SRQPC (0xC00430 >> 2) +#define V3D_SRQUA (0xC00434 >> 2) +#define V3D_SRQCS (0xC0043c >> 2) +#define V3D_DBCFG (0xC00e00 >> 2) +#define V3D_DBQITE (0xC00e2c >> 2) +#define V3D_DBQITC (0xC00e30 >> 2) -#define GPU_FFT_MEM_MAP 0x0 // cached=0x0; direct=0x20000000 +#define GPU_FFT_MEM_MAP 0x0 // cached=0x0; direct=0x20000000 #define GPU_FFT_NO_FLUSH 1 -#define GPU_FFT_TIMEOUT 2000 // ms - -unsigned gpu_fft_base_exec_direct ( - struct GPU_FFT_BASE *base, - unsigned num_qpus) { +#define GPU_FFT_TIMEOUT 2000 // ms +unsigned gpu_fft_base_exec_direct(struct GPU_FFT_BASE* base, unsigned num_qpus) { unsigned q; - base->peri[V3D_DBCFG] = 0; // Disallow IRQ - base->peri[V3D_DBQITE] = 0; // Disable IRQ - base->peri[V3D_DBQITC] = -1; // Resets IRQ flags + base->peri[V3D_DBCFG] = 0; // Disallow IRQ + base->peri[V3D_DBQITE] = 0; // Disable IRQ + base->peri[V3D_DBQITC] = -1; // Resets IRQ flags - base->peri[V3D_L2CACTL] = 1<<2; // Clear L2 cache - base->peri[V3D_SLCACTL] = -1; // Clear other caches + base->peri[V3D_L2CACTL] = 1 << 2; // Clear L2 cache + base->peri[V3D_SLCACTL] = -1; // Clear other caches - base->peri[V3D_SRQCS] = (1<<7) | (1<<8) | (1<<16); // Reset error bit and counts + base->peri[V3D_SRQCS] = (1 << 7) | (1 << 8) | (1 << 16); // Reset error bit and counts - for (q=0; qperi[V3D_SRQUA] = base->vc_unifs[q]; base->peri[V3D_SRQPC] = base->vc_code; } // Busy wait polling for (;;) { - if (((base->peri[V3D_SRQCS]>>16) & 0xff) == num_qpus) break; // All done? + if (((base->peri[V3D_SRQCS] >> 16) & 0xff) == num_qpus) + break; // All done? } return 0; } -unsigned gpu_fft_base_exec( - struct GPU_FFT_BASE *base, - unsigned num_qpus) { - +unsigned gpu_fft_base_exec(struct GPU_FFT_BASE* base, unsigned num_qpus) { if (base->vc_msg) { // Use mailbox // Returns: 0x0 for success; 0x80000000 for timeout return execute_qpu(base->mb, num_qpus, base->vc_msg, GPU_FFT_NO_FLUSH, GPU_FFT_TIMEOUT); - } - else { + } else { // Direct register poking return gpu_fft_base_exec_direct(base, num_qpus); } } -int gpu_fft_alloc ( - int mb, - unsigned size, - struct GPU_FFT_PTR *ptr) { - - struct GPU_FFT_BASE *base; - volatile unsigned *peri; +int gpu_fft_alloc(int mb, unsigned size, struct GPU_FFT_PTR* ptr) { + struct GPU_FFT_BASE* base; + volatile unsigned* peri; unsigned handle; - if (qpu_enable(mb, 1)) return -1; + if (qpu_enable(mb, 1)) + return -1; // Shared memory : cached=0xC; direct=0x4 unsigned mem_flg = bcm_host_get_sdram_address() == 0x40000000 ? 0xC : 0x4; @@ -109,7 +100,7 @@ int gpu_fft_alloc ( return -3; } - peri = (volatile unsigned *) mapmem(bcm_host_get_peripheral_address(), bcm_host_get_peripheral_size()); + peri = (volatile unsigned*)mapmem(bcm_host_get_peripheral_address(), bcm_host_get_peripheral_size()); if (!peri) { mem_free(mb, handle); qpu_enable(mb, 0); @@ -117,18 +108,18 @@ int gpu_fft_alloc ( } ptr->vc = mem_lock(mb, handle); - ptr->arm.vptr = mapmem(BUS_TO_PHYS(ptr->vc+GPU_FFT_MEM_MAP), size); + ptr->arm.vptr = mapmem(BUS_TO_PHYS(ptr->vc + GPU_FFT_MEM_MAP), size); - base = (struct GPU_FFT_BASE *) ptr->arm.vptr; - base->peri = peri; - base->mb = mb; + base = (struct GPU_FFT_BASE*)ptr->arm.vptr; + base->peri = peri; + base->mb = mb; base->handle = handle; - base->size = size; + base->size = size; return 0; } -void gpu_fft_base_release(struct GPU_FFT_BASE *base) { +void gpu_fft_base_release(struct GPU_FFT_BASE* base) { int mb = base->mb; unsigned handle = base->handle, size = base->size; unmapmem((void*)base->peri, bcm_host_get_peripheral_size()); @@ -138,10 +129,7 @@ void gpu_fft_base_release(struct GPU_FFT_BASE *base) { qpu_enable(mb, 0); } -unsigned gpu_fft_ptr_inc ( - struct GPU_FFT_PTR *ptr, - int bytes) { - +unsigned gpu_fft_ptr_inc(struct GPU_FFT_PTR* ptr, int bytes) { unsigned vc = ptr->vc; ptr->vc += bytes; ptr->arm.bptr += bytes; diff --git a/src/hello_fft/gpu_fft_shaders.c b/src/hello_fft/gpu_fft_shaders.c index 83eb31d..18c0c85 100644 --- a/src/hello_fft/gpu_fft_shaders.c +++ b/src/hello_fft/gpu_fft_shaders.c @@ -27,72 +27,58 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ static unsigned int shader_256[] = { - #include "hex/shader_256.hex" +#include "hex/shader_256.hex" }; static unsigned int shader_512[] = { - #include "hex/shader_512.hex" +#include "hex/shader_512.hex" }; static unsigned int shader_1k[] = { - #include "hex/shader_1k.hex" +#include "hex/shader_1k.hex" }; static unsigned int shader_2k[] = { - #include "hex/shader_2k.hex" +#include "hex/shader_2k.hex" }; static unsigned int shader_4k[] = { - #include "hex/shader_4k.hex" +#include "hex/shader_4k.hex" }; static unsigned int shader_8k[] = { - #include "hex/shader_8k.hex" +#include "hex/shader_8k.hex" }; static unsigned int shader_16k[] = { - #include "hex/shader_16k.hex" +#include "hex/shader_16k.hex" }; static unsigned int shader_32k[] = { - #include "hex/shader_32k.hex" +#include "hex/shader_32k.hex" }; static unsigned int shader_64k[] = { - #include "hex/shader_64k.hex" +#include "hex/shader_64k.hex" }; static unsigned int shader_128k[] = { - #include "hex/shader_128k.hex" +#include "hex/shader_128k.hex" }; static unsigned int shader_256k[] = { - #include "hex/shader_256k.hex" +#include "hex/shader_256k.hex" }; static unsigned int shader_512k[] = { - #include "hex/shader_512k.hex" +#include "hex/shader_512k.hex" }; static unsigned int shader_1024k[] = { - #include "hex/shader_1024k.hex" +#include "hex/shader_1024k.hex" }; static unsigned int shader_2048k[] = { - #include "hex/shader_2048k.hex" +#include "hex/shader_2048k.hex" }; static struct { unsigned int size, *code; -} -shaders[] = { - {sizeof(shader_256), shader_256}, - {sizeof(shader_512), shader_512}, - {sizeof(shader_1k), shader_1k}, - {sizeof(shader_2k), shader_2k}, - {sizeof(shader_4k), shader_4k}, - {sizeof(shader_8k), shader_8k}, - {sizeof(shader_16k), shader_16k}, - {sizeof(shader_32k), shader_32k}, - {sizeof(shader_64k), shader_64k}, - {sizeof(shader_128k), shader_128k}, - {sizeof(shader_256k), shader_256k}, - {sizeof(shader_512k), shader_512k}, - {sizeof(shader_1024k), shader_1024k}, - {sizeof(shader_2048k), shader_2048k} -}; +} shaders[] = {{sizeof(shader_256), shader_256}, {sizeof(shader_512), shader_512}, {sizeof(shader_1k), shader_1k}, {sizeof(shader_2k), shader_2k}, {sizeof(shader_4k), shader_4k}, + {sizeof(shader_8k), shader_8k}, {sizeof(shader_16k), shader_16k}, {sizeof(shader_32k), shader_32k}, {sizeof(shader_64k), shader_64k}, {sizeof(shader_128k), shader_128k}, + {sizeof(shader_256k), shader_256k}, {sizeof(shader_512k), shader_512k}, {sizeof(shader_1024k), shader_1024k}, {sizeof(shader_2048k), shader_2048k}}; -unsigned int gpu_fft_shader_size(int log2_N) { - return shaders[log2_N-8].size; +unsigned int gpu_fft_shader_size(int log2_N) { + return shaders[log2_N - 8].size; } -unsigned int *gpu_fft_shader_code(int log2_N) { - return shaders[log2_N-8].code; +unsigned int* gpu_fft_shader_code(int log2_N) { + return shaders[log2_N - 8].code; } diff --git a/src/hello_fft/gpu_fft_twiddles.c b/src/hello_fft/gpu_fft_twiddles.c index 3b7d85f..f626e20 100644 --- a/src/hello_fft/gpu_fft_twiddles.c +++ b/src/hello_fft/gpu_fft_twiddles.c @@ -30,263 +30,249 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "gpu_fft.h" -#define ALPHA(dx) (2*pow(sin((dx)/2),2)) -#define BETA(dx) (sin(dx)) +#define ALPHA(dx) (2 * pow(sin((dx) / 2), 2)) +#define BETA(dx) (sin(dx)) -static double k[16] = {0,8,4,4,2,2,2,2,1,1,1,1,1,1,1,1}; -static double m[16] = {0,0,0,1,0,1,2,3,0,1,2,3,4,5,6,7}; +static double k[16] = {0, 8, 4, 4, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1}; +static double m[16] = {0, 0, 0, 1, 0, 1, 2, 3, 0, 1, 2, 3, 4, 5, 6, 7}; /****************************************************************************/ -static float *twiddles_base_16(double two_pi, float *out, double theta) { +static float* twiddles_base_16(double two_pi, float* out, double theta) { int i; - for (i=0; i<16; i++) { - *out++ = cos(two_pi/16*k[i]*m[i] + theta*k[i]); - *out++ = sin(two_pi/16*k[i]*m[i] + theta*k[i]); + for (i = 0; i < 16; i++) { + *out++ = cos(two_pi / 16 * k[i] * m[i] + theta * k[i]); + *out++ = sin(two_pi / 16 * k[i] * m[i] + theta * k[i]); } return out; } -static float *twiddles_base_32(double two_pi, float *out, double theta) { +static float* twiddles_base_32(double two_pi, float* out, double theta) { int i; - for (i=0; i<16; i++) { - *out++ = cos(two_pi/32*i + theta); - *out++ = sin(two_pi/32*i + theta); + for (i = 0; i < 16; i++) { + *out++ = cos(two_pi / 32 * i + theta); + *out++ = sin(two_pi / 32 * i + theta); } - return twiddles_base_16(two_pi, out, 2*theta); + return twiddles_base_16(two_pi, out, 2 * theta); } -static float *twiddles_base_64(double two_pi, float *out) { +static float* twiddles_base_64(double two_pi, float* out) { int i; - for (i=0; i<32; i++) { - *out++ = cos(two_pi/64*i); - *out++ = sin(two_pi/64*i); + for (i = 0; i < 32; i++) { + *out++ = cos(two_pi / 64 * i); + *out++ = sin(two_pi / 64 * i); } return twiddles_base_32(two_pi, out, 0); } /****************************************************************************/ -static float *twiddles_step_16(double /*two_pi*/, float *out, double theta) { +static float* twiddles_step_16(double /*two_pi*/, float* out, double theta) { int i; - for (i=0; i<16; i++) { - *out++ = ALPHA(theta*k[i]); - *out++ = BETA(theta*k[i]); + for (i = 0; i < 16; i++) { + *out++ = ALPHA(theta * k[i]); + *out++ = BETA(theta * k[i]); } return out; } -static float *twiddles_step_32(double two_pi, float *out, double theta) { +static float* twiddles_step_32(double two_pi, float* out, double theta) { int i; - for (i=0; i<16; i++) { + for (i = 0; i < 16; i++) { *out++ = ALPHA(theta); - *out++ = BETA(theta); + *out++ = BETA(theta); } - return twiddles_step_16(two_pi, out, 2*theta); + return twiddles_step_16(two_pi, out, 2 * theta); } /****************************************************************************/ -static void twiddles_256(double two_pi, float *out) { - double N=256; +static void twiddles_256(double two_pi, float* out) { + double N = 256; int q; out = twiddles_base_16(two_pi, out, 0); - out = twiddles_step_16(two_pi, out, two_pi/N * GPU_FFT_QPUS); + out = twiddles_step_16(two_pi, out, two_pi / N * GPU_FFT_QPUS); - for (q=0; q21) return -1; - *shared = shaders[log2_N-8].shared; - *unique = shaders[log2_N-8].unique; - *passes = shaders[log2_N-8].passes; + void (*twiddles)(double, float*); +} shaders[] = {{2, 2, 1, twiddles_256}, {2, 3, 1, twiddles_512}, {2, 4, 2, twiddles_1k}, {2, 6, 2, twiddles_2k}, {3, 3, 1, twiddles_4k}, {3, 4, 1, twiddles_8k}, {3, 5, 1, twiddles_16k}, + {3, 6, 2, twiddles_32k}, {3, 8, 2, twiddles_64k}, {4, 5, 1, twiddles_128k}, {4, 6, 2, twiddles_256k}, {4, 7, 2, twiddles_512k}, {4, 8, 2, twiddles_1024k}, {4, 10, 2, twiddles_2048k}}; + +int gpu_fft_twiddle_size(int log2_N, int* shared, int* unique, int* passes) { + if (log2_N < 8 || log2_N > 21) + return -1; + *shared = shaders[log2_N - 8].shared; + *unique = shaders[log2_N - 8].unique; + *passes = shaders[log2_N - 8].passes; return 0; } -void gpu_fft_twiddle_data(int log2_N, int direction, float *out) { - shaders[log2_N-8].twiddles((direction==GPU_FFT_FWD?-2:2)*GPU_FFT_PI, out); +void gpu_fft_twiddle_data(int log2_N, int direction, float* out) { + shaders[log2_N - 8].twiddles((direction == GPU_FFT_FWD ? -2 : 2) * GPU_FFT_PI, out); } diff --git a/src/hello_fft/mailbox.c b/src/hello_fft/mailbox.c index c1a94ee..12e12b2 100644 --- a/src/hello_fft/mailbox.c +++ b/src/hello_fft/mailbox.c @@ -27,235 +27,222 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include -#include -#include -#include -#include -#include -#include #include +#include #include -#include +#include +#include +#include #include +#include #include +#include +#include #include "mailbox.h" -#define PAGE_SIZE (4*1024) - -void *mapmem(unsigned base, unsigned size) -{ - int mem_fd; - unsigned offset = base % PAGE_SIZE; - base = base - offset; - /* open /dev/mem */ - if ((mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0) { - log(LOG_CRIT, "mapmem(): can't open /dev/mem: %s\n", strerror(errno)); - exit (-1); - } - void *mem = mmap( - 0, - size, - PROT_READ|PROT_WRITE, - MAP_SHARED/*|MAP_FIXED*/, - mem_fd, - base); +#define PAGE_SIZE (4 * 1024) + +void* mapmem(unsigned base, unsigned size) { + int mem_fd; + unsigned offset = base % PAGE_SIZE; + base = base - offset; + /* open /dev/mem */ + if ((mem_fd = open("/dev/mem", O_RDWR | O_SYNC)) < 0) { + log(LOG_CRIT, "mapmem(): can't open /dev/mem: %s\n", strerror(errno)); + exit(-1); + } + void* mem = mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED /*|MAP_FIXED*/, mem_fd, base); #ifdef GPU_FFT_DEBUG - printf("base=0x%x, mem=%p\n", base, mem); + printf("base=0x%x, mem=%p\n", base, mem); #endif - if (mem == MAP_FAILED) { - log(LOG_CRIT, "mapmem(): mmap error: %s\n", strerror(errno)); - exit (-1); - } - close(mem_fd); - return (char *)mem + offset; + if (mem == MAP_FAILED) { + log(LOG_CRIT, "mapmem(): mmap error: %s\n", strerror(errno)); + exit(-1); + } + close(mem_fd); + return (char*)mem + offset; } -void unmapmem(void *addr, unsigned size) -{ - int s = munmap(addr, size); - if (s != 0) { - log(LOG_CRIT, "unmapmem(): munmap error: %s\n", strerror(errno)); - exit (-1); - } +void unmapmem(void* addr, unsigned size) { + int s = munmap(addr, size); + if (s != 0) { + log(LOG_CRIT, "unmapmem(): munmap error: %s\n", strerror(errno)); + exit(-1); + } } /* * use ioctl to send mbox property message */ -static int mbox_property(int file_desc, void *buf) -{ - int ret_val = ioctl(file_desc, IOCTL_MBOX_PROPERTY, buf); +static int mbox_property(int file_desc, void* buf) { + int ret_val = ioctl(file_desc, IOCTL_MBOX_PROPERTY, buf); - if (ret_val < 0) { - log(LOG_ERR, "mbox_property(): ioctl_set_msg failed: %s\n", strerror(errno)); - } + if (ret_val < 0) { + log(LOG_ERR, "mbox_property(): ioctl_set_msg failed: %s\n", strerror(errno)); + } #ifdef GPU_FFT_DEBUG - unsigned *p = buf; int i; unsigned size = *(unsigned *)buf; - for (i=0; i Date: Wed, 7 Feb 2024 08:54:12 -0800 Subject: [PATCH 9/9] add code formatting badge to readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2f57462..f8d5206 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ ![main](https://github.com/charlie-foxtrot/RTLSDR-Airband/actions/workflows/ci_build.yml/badge.svg?branch=main) ![main](https://github.com/charlie-foxtrot/RTLSDR-Airband/actions/workflows/platform_build.yml/badge.svg?branch=main) ![main](https://github.com/charlie-foxtrot/RTLSDR-Airband/actions/workflows/build_docker_containers.yml/badge.svg?branch=main) +![main](https://github.com/charlie-foxtrot/RTLSDR-Airband/actions/workflows/code_formatting.yml/badge.svg?branch=main) NOTE: Changes as of v5.0.0: - PRs will be opened directly against `main` and the `unstable` branch will no longer be used