mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2024-12-13 19:54:39 +00:00
POCSAG2 Revised bit extractor (#1439)
* Better bit extraction WIP * Parallel clock signal detection for bit extraction * Relax clock detection accuracy. * Reset RateInfo state. TODOs
This commit is contained in:
parent
b3312a704a
commit
4926cf8df5
@ -36,7 +36,7 @@ using namespace std;
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
/* Count of bits that differ between the two values. */
|
/* Count of bits that differ between the two values. */
|
||||||
uint8_t differ_bit_count(uint32_t left, uint32_t right) {
|
uint8_t diff_bit_count(uint32_t left, uint32_t right) {
|
||||||
uint32_t diff = left ^ right;
|
uint32_t diff = left ^ right;
|
||||||
uint8_t count = 0;
|
uint8_t count = 0;
|
||||||
for (size_t i = 0; i < sizeof(diff) * 8; ++i) {
|
for (size_t i = 0; i < sizeof(diff) * 8; ++i) {
|
||||||
@ -127,150 +127,89 @@ uint32_t BitQueue::data() const {
|
|||||||
|
|
||||||
void BitExtractor::extract_bits(const buffer_f32_t& audio) {
|
void BitExtractor::extract_bits(const buffer_f32_t& audio) {
|
||||||
// Assumes input has been normalized +/- 1.0f.
|
// Assumes input has been normalized +/- 1.0f.
|
||||||
|
|
||||||
for (size_t i = 0; i < audio.count; ++i) {
|
for (size_t i = 0; i < audio.count; ++i) {
|
||||||
sample_ = audio.p[i];
|
auto sample = audio.p[i];
|
||||||
++sample_index_;
|
samples_until_next_ -= 1;
|
||||||
|
|
||||||
// There's a transition when both sides of the XOR are the
|
if (!current_rate_) {
|
||||||
// same which will result in a the overall value being 0.
|
// Feed the known rate queues for clock detection.
|
||||||
bool is_transition = ((last_sample_ < 0) ^ (sample_ >= 0)) == 0;
|
for (auto& rate : known_rates_) {
|
||||||
if (is_transition) {
|
if (handle_sample(rate, sample) &&
|
||||||
if (handle_transition())
|
diff_bit_count(rate.bits.data(), clock_magic_number) <= 2) {
|
||||||
bad_transitions_ = 0;
|
// Clock detected.
|
||||||
else
|
// NB: This block should only happen on the second sample of a pulse.
|
||||||
++bad_transitions_;
|
// samples_until_next_ to start sampling the *next* pulse.
|
||||||
|
current_rate_ = &rate;
|
||||||
// Too many bad transitions? Reset.
|
samples_until_next_ = rate.sample_interval;
|
||||||
if (bad_transitions_ > bad_transition_reset_threshold)
|
ready_to_send_ = false;
|
||||||
reset();
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Time to push the next bit?
|
// Have a clock rate and it's time to process the next sample.
|
||||||
if (sample_index_ >= next_bit_center_) {
|
if (current_rate_ && samples_until_next_ <= 0) {
|
||||||
// Use the two most recent samples for the bit value.
|
// TODO: It seems like it would be possible to combine this
|
||||||
auto val = (sample_ + last_sample_) / 2.0;
|
// code with handle_sample. Nearly the same work.
|
||||||
bits_.push(val < 0); // NB: '1' is negative.
|
|
||||||
|
|
||||||
if (current_rate_)
|
// Only send on the second sample of a bit.
|
||||||
next_bit_center_ += current_rate_->bit_length;
|
// Sampling twice helps mitigate noisy audio data.
|
||||||
|
if (ready_to_send_) {
|
||||||
|
auto value = (prev_sample_ + sample) / 2;
|
||||||
|
bits_.push(signbit(value)); // NB: negative == '1'
|
||||||
|
}
|
||||||
|
|
||||||
|
ready_to_send_ = !ready_to_send_;
|
||||||
|
prev_sample_ = sample;
|
||||||
|
samples_until_next_ += current_rate_->sample_interval;
|
||||||
}
|
}
|
||||||
|
|
||||||
last_sample_ = sample_;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void BitExtractor::configure(uint32_t sample_rate) {
|
void BitExtractor::configure(uint32_t sample_rate) {
|
||||||
sample_rate_ = sample_rate;
|
sample_rate_ = sample_rate;
|
||||||
min_valid_length_ = UINT16_MAX;
|
|
||||||
|
|
||||||
// Build the baud rate info table based on the sample rate.
|
// Build the baud rate info table based on the sample rate.
|
||||||
for (auto& info : known_rates_) {
|
// Sampling at 2x the baud rate to synchronize to bit transitions
|
||||||
info.bit_length = sample_rate / info.baud_rate;
|
// without needing to know exact transition boundaries.
|
||||||
|
for (auto& rate : known_rates_)
|
||||||
// Allow for 20% deviation.
|
rate.sample_interval = sample_rate / (2.0 * rate.baud_rate);
|
||||||
info.min_bit_length = 0.80 * info.bit_length;
|
|
||||||
info.max_bit_length = 1.20 * info.bit_length;
|
|
||||||
|
|
||||||
if (info.min_bit_length < min_valid_length_)
|
|
||||||
min_valid_length_ = info.min_bit_length;
|
|
||||||
}
|
|
||||||
|
|
||||||
reset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BitExtractor::reset() {
|
void BitExtractor::reset() {
|
||||||
current_rate_ = nullptr;
|
current_rate_ = nullptr;
|
||||||
rate_misses_ = 0;
|
samples_until_next_ = 0.0;
|
||||||
|
prev_sample_ = 0.0;
|
||||||
|
ready_to_send_ = false;
|
||||||
|
|
||||||
sample_ = 0.0;
|
for (auto& rate : known_rates_) {
|
||||||
last_sample_ = 0.0;
|
rate.samples_until_next = 0.0;
|
||||||
next_bit_center_ = 0.0;
|
rate.last_sample = 0.0;
|
||||||
|
rate.bits.reset();
|
||||||
sample_index_ = 0;
|
}
|
||||||
last_transition_index_ = 0;
|
|
||||||
bad_transitions_ = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t BitExtractor::baud_rate() const {
|
uint16_t BitExtractor::baud_rate() const {
|
||||||
return current_rate_ ? current_rate_->baud_rate : 0;
|
return current_rate_ ? current_rate_->baud_rate : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BitExtractor::handle_transition() {
|
bool BitExtractor::handle_sample(RateInfo& rate, float sample) {
|
||||||
auto length = sample_index_ - last_transition_index_;
|
// TODO: Still getting some clock misses at the start of messages.
|
||||||
last_transition_index_ = sample_index_;
|
rate.samples_until_next -= 1;
|
||||||
|
|
||||||
// Length is too short, ignore this.
|
// Not time to process a sample yet.
|
||||||
if (length <= min_valid_length_) return false;
|
if (rate.samples_until_next > 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
// TODO: should the following be "bad" or "rate misses"?
|
// Sample signs are the same, both samples are in the same bit pulse.
|
||||||
// Is length a multiple of the current rate's bit length?
|
auto has_new_bit = signbit(sample) == signbit(rate.last_sample);
|
||||||
uint16_t bit_count = 0;
|
if (has_new_bit)
|
||||||
if (!count_bits(length, bit_count)) return false;
|
rate.bits.push(signbit(sample)); // NB: negative == '1'
|
||||||
|
|
||||||
// Does the bit length correspond to a known rate?
|
// How long until the next sample?
|
||||||
auto bit_length = length / static_cast<float>(bit_count);
|
rate.samples_until_next += rate.sample_interval;
|
||||||
auto rate = get_baud_info(bit_length);
|
rate.last_sample = sample;
|
||||||
if (!rate) return false;
|
|
||||||
|
|
||||||
// Set current rate if it hasn't been set yet.
|
return has_new_bit;
|
||||||
if (!current_rate_)
|
|
||||||
current_rate_ = rate;
|
|
||||||
|
|
||||||
// Maybe current rate isn't the best rate?
|
|
||||||
auto rate_miss = rate != current_rate_;
|
|
||||||
if (rate_miss) {
|
|
||||||
++rate_misses_;
|
|
||||||
|
|
||||||
// Lots of rate misses, try another rate.
|
|
||||||
if (rate_misses_ > rate_miss_reset_threshold) {
|
|
||||||
current_rate_ = rate;
|
|
||||||
rate_misses_ = 0;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Transition is aligned with the current rate, predict next bit.
|
|
||||||
auto half_bit = current_rate_->bit_length / 2.0;
|
|
||||||
next_bit_center_ = sample_index_ + half_bit;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool BitExtractor::count_bits(uint32_t length, uint16_t& bit_count) {
|
|
||||||
bit_count = 0;
|
|
||||||
|
|
||||||
// No rate yet, assume one valid bit. Downstream will deal with it.
|
|
||||||
if (!current_rate_) {
|
|
||||||
bit_count = 1;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// How many bits span the specified length?
|
|
||||||
float exact_bits = length / current_rate_->bit_length;
|
|
||||||
|
|
||||||
// < 1 bit, current rate is probably too low.
|
|
||||||
if (exact_bits < 0.80) return false;
|
|
||||||
|
|
||||||
// Round to the nearest # of bits and determine how
|
|
||||||
// well the current rate fits the data.
|
|
||||||
float round_bits = std::round(exact_bits);
|
|
||||||
float error = std::abs(exact_bits - round_bits) / exact_bits;
|
|
||||||
|
|
||||||
// Good transition are w/in 15% of current rate estimate.
|
|
||||||
bit_count = round_bits;
|
|
||||||
return error < 0.15;
|
|
||||||
}
|
|
||||||
|
|
||||||
const BitExtractor::BaudInfo* BitExtractor::get_baud_info(float bit_length) const {
|
|
||||||
// NB: This assumes known_rates_ are ordered slowest first.
|
|
||||||
for (const auto& info : known_rates_) {
|
|
||||||
if (bit_length >= info.min_bit_length &&
|
|
||||||
bit_length <= info.max_bit_length) {
|
|
||||||
return &info;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* CodewordExtractor *************************************/
|
/* CodewordExtractor *************************************/
|
||||||
@ -286,9 +225,9 @@ void CodewordExtractor::process_bits() {
|
|||||||
|
|
||||||
// Wait for the sync frame.
|
// Wait for the sync frame.
|
||||||
if (!has_sync_) {
|
if (!has_sync_) {
|
||||||
if (differ_bit_count(data_, sync_codeword) <= 2)
|
if (diff_bit_count(data_, sync_codeword) <= 2)
|
||||||
handle_sync(/*inverted=*/false);
|
handle_sync(/*inverted=*/false);
|
||||||
else if (differ_bit_count(data_, ~sync_codeword) <= 2)
|
else if (diff_bit_count(data_, ~sync_codeword) <= 2)
|
||||||
handle_sync(/*inverted=*/true);
|
handle_sync(/*inverted=*/true);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -84,52 +84,38 @@ class BitExtractor {
|
|||||||
void extract_bits(const buffer_f32_t& audio);
|
void extract_bits(const buffer_f32_t& audio);
|
||||||
void configure(uint32_t sample_rate);
|
void configure(uint32_t sample_rate);
|
||||||
void reset();
|
void reset();
|
||||||
|
|
||||||
uint16_t baud_rate() const;
|
uint16_t baud_rate() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/* Number of rate misses that would cause a rate update. */
|
/* Clock signal detection magic number. */
|
||||||
static constexpr uint8_t rate_miss_reset_threshold = 5;
|
static constexpr uint32_t clock_magic_number = 0xAAAAAAAA;
|
||||||
|
|
||||||
/* Number of rate misses that would cause a rate update. */
|
struct RateInfo {
|
||||||
static constexpr uint8_t bad_transition_reset_threshold = 10;
|
const int16_t baud_rate = 0;
|
||||||
|
float sample_interval = 0.0;
|
||||||
|
|
||||||
struct BaudInfo {
|
float samples_until_next = 0.0;
|
||||||
uint16_t baud_rate = 0;
|
float last_sample = 0.0;
|
||||||
float bit_length = 0.0;
|
BitQueue bits{};
|
||||||
float min_bit_length = 0.0;
|
|
||||||
float max_bit_length = 0.0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Handle a transition, returns true if "good". */
|
/* Updates a rate info with the given sample.
|
||||||
bool handle_transition();
|
* Returns true if the rate info has a new bit in its queue. */
|
||||||
|
bool handle_sample(RateInfo& rate, float sample);
|
||||||
|
|
||||||
/* Count the number of bits the length represents.
|
std::array<RateInfo, 3> known_rates_{
|
||||||
* Returns true if valid given the current baud rate. */
|
RateInfo{512},
|
||||||
bool count_bits(uint32_t length, uint16_t& bit_count);
|
RateInfo{1200},
|
||||||
|
RateInfo{2400}};
|
||||||
/* Gets the best baud info associated with the specified bit length. */
|
|
||||||
const BaudInfo* get_baud_info(float bit_length) const;
|
|
||||||
|
|
||||||
std::array<BaudInfo, 3> known_rates_{
|
|
||||||
BaudInfo{512},
|
|
||||||
BaudInfo{1200},
|
|
||||||
BaudInfo{2400}};
|
|
||||||
|
|
||||||
BitQueue& bits_;
|
BitQueue& bits_;
|
||||||
|
|
||||||
uint32_t sample_rate_ = 0;
|
uint32_t sample_rate_ = 0;
|
||||||
uint16_t min_valid_length_ = 0;
|
RateInfo* current_rate_ = nullptr;
|
||||||
const BaudInfo* current_rate_ = nullptr;
|
|
||||||
uint8_t rate_misses_ = 0;
|
|
||||||
|
|
||||||
float sample_ = 0.0;
|
float samples_until_next_ = 0.0;
|
||||||
float last_sample_ = 0.0;
|
float prev_sample_ = 0.0;
|
||||||
float next_bit_center_ = 0.0;
|
bool ready_to_send_ = false;
|
||||||
|
|
||||||
uint32_t sample_index_ = 0;
|
|
||||||
uint32_t last_transition_index_ = 0;
|
|
||||||
uint32_t bad_transitions_ = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Extracts codeword batches from the BitQueue. */
|
/* Extracts codeword batches from the BitQueue. */
|
||||||
|
Loading…
Reference in New Issue
Block a user