/* -*- c++ -*- */ /* * Copyright 2014,2016 Free Software Foundation, Inc. * * This file is part of GNU Radio * * SPDX-License-Identifier: GPL-3.0-or-later * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "atsc_sync_impl.h" #include "atsc_types.h" #include <gnuradio/io_signature.h> namespace gr { namespace dtv { static const double LOOP_FILTER_TAP = 0.0005; // 0.0005 works static const double ADJUSTMENT_GAIN = 1.0e-5 / (10 * ATSC_DATA_SEGMENT_LENGTH); static const int SYMBOL_INDEX_OFFSET = 3; static const int MIN_SEG_LOCK_CORRELATION_VALUE = 5; static const signed char SSI_MIN = -16; static const signed char SSI_MAX = 15; atsc_sync::sptr atsc_sync::make(float rate) { return gnuradio::make_block_sptr<atsc_sync_impl>(rate); } atsc_sync_impl::atsc_sync_impl(float rate) : gr::block("dtv_atsc_sync", io_signature::make(1, 1, sizeof(float)), io_signature::make(1, 1, ATSC_DATA_SEGMENT_LENGTH * sizeof(float))), d_rx_clock_to_symbol_freq(rate / ATSC_SYMBOL_RATE), d_si(0) { d_loop.set_taps(LOOP_FILTER_TAP); reset(); } void atsc_sync_impl::reset() { d_w = d_rx_clock_to_symbol_freq; d_mu = 0.5; d_timing_adjust = 0; d_counter = 0; d_symbol_index = 0; d_seg_locked = false; d_sr = 0; memset(d_sample_mem, 0, ATSC_DATA_SEGMENT_LENGTH * sizeof(*d_sample_mem)); // (float)0 = 0x00000000 memset(d_data_mem, 0, ATSC_DATA_SEGMENT_LENGTH * sizeof(*d_data_mem)); // (float)0 = 0x00000000 memset(d_integrator, SSI_MIN, ATSC_DATA_SEGMENT_LENGTH * sizeof(*d_integrator)); // signed char } atsc_sync_impl::~atsc_sync_impl() {} void atsc_sync_impl::forecast(int noutput_items, gr_vector_int& ninput_items_required) { unsigned ninputs = ninput_items_required.size(); for (unsigned i = 0; i < ninputs; i++) ninput_items_required[i] = static_cast<int>(noutput_items * d_rx_clock_to_symbol_freq * ATSC_DATA_SEGMENT_LENGTH) + 1500 - 1; } int atsc_sync_impl::general_work(int noutput_items, gr_vector_int& ninput_items, gr_vector_const_void_star& input_items, gr_vector_void_star& output_items) { const float* in = static_cast<const float*>(input_items[0]); float* out = static_cast<float*>(output_items[0]); float interp_sample; // amount actually consumed d_si = 0; for (d_output_produced = 0; d_output_produced < noutput_items && (d_si + (int)d_interp.ntaps()) < ninput_items[0];) { // First we interpolate a sample from input to work with interp_sample = d_interp.interpolate(&in[d_si], d_mu); // Apply our timing adjustment slowly over several samples d_mu += ADJUSTMENT_GAIN * 1e3 * d_timing_adjust; double s = d_mu + d_w; double float_incr = floor(s); d_mu = s - float_incr; d_incr = (int)float_incr; assert(d_incr >= 1 && d_incr <= 3); d_si += d_incr; // Remember the sample at this count position d_sample_mem[d_counter] = interp_sample; // Is the sample positive or negative? int bit = (interp_sample < 0 ? 0 : 1); // Put the sign bit into our shift register d_sr = ((bit & 1) << 3) | (d_sr >> 1); // When +,-,-,+ (0x9, 1001) samples show up we have likely found a segment // sync, it is more likely the segment sync will show up at about the same // spot every ATSC_DATA_SEGMENT_LENGTH samples so we add some weight // to this spot every pass to prevent random +,-,-,+ symbols from // confusing our synchronizer d_integrator[d_counter] += ((d_sr == 0x9) ? +2 : -1); if (d_integrator[d_counter] < SSI_MIN) d_integrator[d_counter] = SSI_MIN; if (d_integrator[d_counter] > SSI_MAX) d_integrator[d_counter] = SSI_MAX; d_symbol_index++; if (d_symbol_index >= ATSC_DATA_SEGMENT_LENGTH) d_symbol_index = 0; d_counter++; if (d_counter >= ATSC_DATA_SEGMENT_LENGTH) { // counter just wrapped... int best_correlation_value = d_integrator[0]; int best_correlation_index = 0; for (int i = 1; i < ATSC_DATA_SEGMENT_LENGTH; i++) if (d_integrator[i] > best_correlation_value) { best_correlation_value = d_integrator[i]; best_correlation_index = i; } d_seg_locked = best_correlation_value >= MIN_SEG_LOCK_CORRELATION_VALUE; // the coefficients are -1,-1,+1,+1 // d_timing_adjust = d_sample_mem[best_correlation_index - 3] + // d_sample_mem[best_correlation_index - 2] - // d_sample_mem[best_correlation_index - 1] - // d_sample_mem[best_correlation_index]; int corr_count = best_correlation_index; d_timing_adjust = -d_sample_mem[corr_count--]; if (corr_count < 0) corr_count = ATSC_DATA_SEGMENT_LENGTH - 1; d_timing_adjust -= d_sample_mem[corr_count--]; if (corr_count < 0) corr_count = ATSC_DATA_SEGMENT_LENGTH - 1; d_timing_adjust += d_sample_mem[corr_count--]; if (corr_count < 0) corr_count = ATSC_DATA_SEGMENT_LENGTH - 1; d_timing_adjust += d_sample_mem[corr_count--]; d_symbol_index = SYMBOL_INDEX_OFFSET - 1 - best_correlation_index; if (d_symbol_index < 0) d_symbol_index += ATSC_DATA_SEGMENT_LENGTH; d_counter = 0; } // If we are locked we can start filling and producing data packets // Due to the way we lock the first data packet will almost always be // half full, this is OK because the fs_checker will not let packets though // until a non-corrupted field packet is found if (d_seg_locked) { d_data_mem[d_symbol_index] = interp_sample; if (d_symbol_index >= (ATSC_DATA_SEGMENT_LENGTH - 1)) { memcpy(&out[d_output_produced * ATSC_DATA_SEGMENT_LENGTH], d_data_mem, ATSC_DATA_SEGMENT_LENGTH * sizeof(float)); d_output_produced++; } } } consume_each(d_si); return d_output_produced; } } /* namespace dtv */ } /* namespace gr */