/* Copyright (C) 2015 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DPD. ODR-DPD 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. ODR-DPD 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 ODR-DPD. If not, see . */ #include "AlignSample.hpp" #include namespace fftw { class plan { public: template plan(size_t N, T& in, T& out, int direction) { m_plan = fftwf_plan_dft_1d(N, reinterpret_cast(&in.front()), reinterpret_cast(&out.front()), FFTW_FORWARD, FFTW_MEASURE); } plan(const plan& other) = delete; const plan& operator=(const plan& other) = delete; void execute(void) { fftwf_execute(m_plan); } ~plan() { fftwf_destroy_plan(m_plan); } private: fftwf_plan m_plan; }; } void AlignSample::push_tx_samples(complexf* samps, size_t len, double first_sample_time) { std::lock_guard lock(m_mutex); std::copy(samps, samps + len, std::back_inserter(m_txsamples)); if (m_first_tx_sample_time == 0) { m_first_tx_sample_time = first_sample_time; } } void AlignSample::push_rx_samples(complexf* samps, size_t len, double first_sample_time) { std::lock_guard lock(m_mutex); if (m_first_rx_sample_time == 0 and first_sample_time != 0) { std::copy(samps, samps + len, std::back_inserter(m_rxsamples)); m_first_rx_sample_time = first_sample_time; m_num_rx_samples_dropped = 0; } else if (m_first_rx_sample_time != 0) { // We have previously received samples with valid timestamp double delta = m_rx_sample_time(m_rxsamples.size()) - first_sample_time; if (std::fabs(delta) > 1e-3) { fprintf(stderr, "RX sample time %f expected %f. Delta of %f(%zu)." " Resetting RX\n", m_rx_sample_time(m_rxsamples.size()), first_sample_time, delta, (size_t)std::fabs(delta * (double)samplerate)); m_first_rx_sample_time = 0; m_num_rx_samples_dropped = 0; m_rxsamples.clear(); } std::copy(samps, samps + len, std::back_inserter(m_rxsamples)); } else if (first_sample_time == 0) { MDEBUG("RX timestamp missing\n"); throw std::runtime_error("RX timestamp missing"); } } void AlignSample::reset_rx() { std::lock_guard lock(m_mutex); m_first_rx_sample_time = 0; m_num_rx_samples_dropped = 0; m_rxsamples.clear(); } bool AlignSample::ready(size_t min_samples) { std::lock_guard lock(m_mutex); return align() and m_rxsamples.size() > min_samples and m_txsamples.size() > min_samples; } CorrelationResult AlignSample::crosscorrelate(size_t len) { double rx_ts = 0; double tx_ts = 0; /* The size of the FFT is twice as long as the desired * correlation length, because the FFT does a circular * correlation, and we therefore pad half with zeros */ const size_t N = 2 * len; std::vector rx_fft_in(N); std::vector rx_fft_out(N); std::vector tx_fft_in(N); std::vector tx_fft_out(N); std::vector ifft_in(N); std::vector ifft_out(N); fftw::plan rx_fft_plan(N, rx_fft_in, rx_fft_out, FFTW_FORWARD); fftw::plan tx_fft_plan(N, tx_fft_in, tx_fft_out, FFTW_FORWARD); fftw::plan ifft_plan(N, ifft_in, ifft_out, FFTW_BACKWARD); // Do a quick copy, so as to free the mutex { std::lock_guard lock(m_mutex); if (!align() or m_rxsamples.size() < len or m_txsamples.size() < len) { CorrelationResult result(0); return result; } std::copy(m_rxsamples.begin(), m_rxsamples.begin() + len, rx_fft_in.begin()); std::copy(m_txsamples.begin(), m_txsamples.begin() + len, tx_fft_in.begin()); // the other half of the buffers are set to 0 m_drop_rx_samples(len); m_drop_tx_samples(len); rx_ts = m_rx_sample_time(); tx_ts = m_tx_sample_time(); } CorrelationResult result(len); result.rx_timestamp = rx_ts; result.tx_timestamp = tx_ts; // Calculate power for (size_t i = 0; i < len; i++) { result.rx_power += std::norm(rx_fft_in[i]); } result.rx_power = std::sqrt(result.rx_power); for (size_t i = 0; i < len; i++) { result.tx_power += std::norm(tx_fft_in[i]); } result.tx_power = std::sqrt(result.tx_power); // Implement // corr(a, b) = ifft(fft(a) * conj(fft(b))) // Attention: circular correlation ! rx_fft_plan.execute(); tx_fft_plan.execute(); for (size_t i = 0; i < len; i++) { ifft_in[i] = tx_fft_out[i] * std::conj(rx_fft_out[i]); } ifft_plan.execute(); for (size_t i = 0; i < len; i++) { result.correlation[i] = ifft_out[i]; } return result; } std::pair, std::vector > AlignSample::get_samples( size_t len, size_t rx_delay) { std::pair, std::vector > rval; std::lock_guard lock(m_mutex); if (align() and m_rxsamples.size() > len + rx_delay and m_txsamples.size() > len) { rval.first.reserve(len); rval.second.reserve(len); std::copy(m_rxsamples.begin() + rx_delay, m_rxsamples.begin() + rx_delay + len, std::back_inserter(rval.first)); std::copy(m_txsamples.begin(), m_txsamples.begin() + len, std::back_inserter(rval.second)); } return rval; } void AlignSample::consume(size_t samples) { std::lock_guard lock(m_mutex); if (align() and m_rxsamples.size() > samples and m_txsamples.size() > samples) { m_drop_rx_samples(samples); m_drop_tx_samples(samples); } } bool AlignSample::align() { if (std::abs(m_rx_sample_time() - m_tx_sample_time()) < 1e-6) { return true; } else if (m_rx_sample_time() < m_tx_sample_time()) { size_t rx_samples_to_skip = (m_tx_sample_time() - m_rx_sample_time()) * samplerate; if (rx_samples_to_skip > m_rxsamples.size()) { return false; } m_drop_rx_samples(rx_samples_to_skip); return true; } else if (m_rx_sample_time() > m_tx_sample_time()) { size_t tx_samples_to_skip = (m_rx_sample_time() - m_tx_sample_time()) * samplerate; if (tx_samples_to_skip > m_txsamples.size()) { return false; } m_drop_tx_samples(tx_samples_to_skip); return true; } return false; }