Frame splitting in 2s_lora_walkie_talkie example
R2AirVlad opened this issue · 9 comments
Hi!
Could you please explain this code part in i2s_lora_walkie_talkie example?
// split by frame, decode and play
for (int i = 0; i < packet_size; i++) {
c2_bits_[i % c2_bytes_per_frame_] = lora_radio_rx_queue_.shift();
if (i % c2_bytes_per_frame_ == c2_bytes_per_frame_ - 1) {
codec2_decode(c2_, c2_samples_, c2_bits_);
i2s_write(I2S_NUM_0, c2_samples_, sizeof(uint16_t) * c2_samples_per_frame_, &bytes_written, portMAX_DELAY);
vTaskDelay(1);
I experimenting with this code and notased that it return every 3-d byte from codec2 queue (450 mode), but we need every byte. Or not? This works fine, but why?
At bottom my extended example of loopback codec2 with packaging by 48 bytes and read back again. Please run it for detail understanding.
Based on this algorithm its possible to add a half-duplex repeater function to loraDV radio...
#include <driver/i2s.h>
#include <codec2.h>
#include <CircularBuffer.h>
// serial
#define SERIAL_BAUD_RATE 115200
// audio speaker
#define AUDIO_SPEAKER_BCLK 26
#define AUDIO_SPEAKER_LRC 25
#define AUDIO_SPEAKER_DIN 22
// audio microphone
#define AUDIO_MIC_SD 27
#define AUDIO_MIC_WS 14
#define AUDIO_MIC_SCK 13
#define AUDIO_SAMPLE_RATE 8000 // 44100
#define QUEUE_LEN 512
CircularBuffer<uint8_t, QUEUE_LEN> tx_rx_queue_;
CircularBuffer<uint8_t, QUEUE_LEN> tx_rx_queue_index_;
TaskHandle_t audio_task_;
struct CODEC2* c2_;
int c2_samples_per_frame_;
int c2_bytes_per_frame_;
int16_t *c2_samples_;
uint8_t *c2_bits_;
void setup() {
Serial.begin(SERIAL_BAUD_RATE);
while (!Serial);
Serial.println(F("Board setup started"));
// create i2s speaker
i2s_config_t i2s_speaker_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
.sample_rate = AUDIO_SAMPLE_RATE,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 8,
.dma_buf_len = 1024,
.use_apll=0,
.tx_desc_auto_clear= true,
.fixed_mclk=-1
};
i2s_pin_config_t i2s_speaker_pin_config = {
.bck_io_num = AUDIO_SPEAKER_BCLK,
.ws_io_num = AUDIO_SPEAKER_LRC,
.data_out_num = AUDIO_SPEAKER_DIN,
.data_in_num = I2S_PIN_NO_CHANGE
};
if (i2s_driver_install(I2S_NUM_0, &i2s_speaker_config, 0, NULL) != ESP_OK) {
Serial.println(F("Failed to install i2s speaker driver"));
}
if (i2s_set_pin(I2S_NUM_0, &i2s_speaker_pin_config) != ESP_OK) {
Serial.println(F("Failed to set i2s speaker pins"));
}
// create i2s microphone
i2s_config_t i2s_mic_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
.sample_rate = AUDIO_SAMPLE_RATE,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 8,
.dma_buf_len = 1024,
.use_apll=0,
.tx_desc_auto_clear= true,
.fixed_mclk=-1
};
i2s_pin_config_t i2s_mic_pin_config = {
.bck_io_num = AUDIO_MIC_SCK,
.ws_io_num = AUDIO_MIC_WS,
.data_out_num = I2S_PIN_NO_CHANGE,
.data_in_num = AUDIO_MIC_SD
};
if (i2s_driver_install(I2S_NUM_1, &i2s_mic_config, 0, NULL) != ESP_OK) {
Serial.println(F("Failed to install i2s mic driver"));
}
if (i2s_set_pin(I2S_NUM_1, &i2s_mic_pin_config) != ESP_OK) {
Serial.println(F("Failed to set i2s mic pins"));
}
// run codec2 audio loopback on a separate task
xTaskCreate(&audio_task, "audio_task", 32000, NULL, 5, &audio_task_);
Serial.println(F("Board setup completed"));
}
void audio_task(void *param) {
// construct and configure codec2
c2_ = codec2_create(CODEC2_MODE_3200);
if (c2_ == NULL) {
Serial.println(F("Failed to create Codec2"));
return;
} else {
//codec2_set_lpc_post_filter(c2_, 1, 0, 0.8, 0.2);
c2_samples_per_frame_ = codec2_samples_per_frame(c2_);
c2_bytes_per_frame_ = codec2_bytes_per_frame(c2_);
c2_samples_ = (int16_t*)malloc(sizeof(int16_t) * c2_samples_per_frame_);
c2_bits_ = (uint8_t*)malloc(sizeof(uint8_t) * c2_bytes_per_frame_);
Serial.println(F("Codec2 constructed"));
Serial.print(F("Semples per frame-"));
Serial.println(c2_samples_per_frame_);
Serial.print(F("Bytes per frame-"));
Serial.println(c2_bytes_per_frame_);
}
// run loopback record-encode-decode-playback loop
size_t bytes_read, bytes_written;
int packet_size = 0;
Serial.println(F("Audio task started"));
while(true) {
if (tx_rx_queue_index_.size() != 0) { // Then the mark of 48 bytes exsist in index queue
int packet_size_rx = tx_rx_queue_index_.shift(); // take the mark of 48 bytes is present in frames queue
Serial.println(F("Packet Recived"));
// devided by frames and play
for (int i = 0; i < packet_size_rx; i++) { // ??? HOW ITS WORK ???
c2_bits_[i % c2_bytes_per_frame_] = tx_rx_queue_.shift(); // ??? HOW ITS WORK ???
if (i % c2_bytes_per_frame_ == c2_bytes_per_frame_ - 1) { // ??? HOW ITS WORK ???
codec2_decode(c2_, c2_samples_, c2_bits_);
i2s_write(I2S_NUM_0, c2_samples_, sizeof(uint16_t) * c2_samples_per_frame_, &bytes_written, portMAX_DELAY);
Serial.print(F("RX byte - "));
Serial.println(*c2_bits_);
vTaskDelay(1);
} // Why we have extract every 8-th byte from bytes queue? Why? ??? HOW ITS WORK ???
} // All 48 bytes extracted from queue and played
Serial.println(F("Packet Played"));
} // Played all 48 bytes of queue
else {
// If enoth bytes - 48 - we can decodd
if (packet_size + c2_bytes_per_frame_ > 48) {
tx_rx_queue_index_.push(packet_size); // push to queue what we have 48 bytes of packet for decodd by codec2
packet_size = 0; // we have sended 48 bytes packet and start to accumulate new 48 bytes packet
Serial.println(F("Packet Done"));
}
// read and codding one frame
size_t bytes_read;
i2s_read(I2S_NUM_1, c2_samples_, sizeof(uint16_t) * c2_samples_per_frame_, &bytes_read, portMAX_DELAY); // read I2S sending semples to codec2
codec2_encode(c2_, c2_bits_, c2_samples_); // codding semples - return one byte to another
for (int n = 0; n < c2_bytes_per_frame_; n++) {
tx_rx_queue_.push(c2_bits_[n]); // push to circular bufer n byte, n+1 byte, n+2 byte, and etc.
Serial.print(F("TX byte - "));
Serial.println(*c2_bits_);
}
packet_size += c2_bytes_per_frame_; // +3 add to packet size each frame coded (for 450 Codec2 mode, + 8 for 3200 mode)
Serial.print(F("Packet size cumulative - "));
Serial.println(packet_size);
vTaskDelay(1);
}
}
}
void loop() {
delay(100);
}
It iterates over the packet and reads audio frames, when last byte from the current frame is read it will decode complete frame and send it over to i2s for playback. To have repeater operation you need second modem with frequency split, because you cannot send and receiver over the same modem at the same time unless some form of time slots are employed where you send on time T and receive on time T + 1.
It iterates over the packet and reads audio frames, when last byte from the current frame is read it will decode complete frame and send it over to i2s for playback.
Ok, but for what we need working codec2 frames if we have 48-bytes packet at all... All frames at packet is sequenced each over and it will be decoded fine... Then we got 48 bytes packet we can start playing all the packets bytes, absolutely no need to split it for frames -- but in case of voice delay maybe justified... I don't know
To have repeater operation you need second modem with frequency split, because you cannot send and receiver over the same modem at the same time unless some form of time slots are employed where you send on time T and receive on time T + 1.
I mean a half-duplex - just receive and immediately transmit the received packet. If packets is small and retransmits as fast as working speed codec2 is should be works.
If multiple Codec2 encoded audio frames are aggregated into one packet then receiver needs to split, because Codec2 requires frames of specific size, it cannot decode complete packet containing multiple audio frames. It is possible to send packet containing one audio frame, but if such small packet is re-transmitted over e.g. KISS Bluetooth then there are delays, because Bluetooth is not efficient in operating on such stream of small packets.
For half-duplex, a.k.a. "parrot" you need to accumulate number of packets, wait for some time until no more data is received and then re-transmit complete buffer (this is how analog "parrots" are operating). So you need 2-2.5x channel time for each user's 1x transmission. If you want to re-transmit each packet immediately then sender (and other users) must know that he should wait for a packet duration, otherwise there will be interference, it requires some kind of time slot mechanism and you will need at least 2x bandwidth on the channel. Taking this complexity into account, it might be easier for repeater to operate full duplex with two modems and clients to operate half duplex with frequency split?
If multiple Codec2 encoded audio frames are aggregated into one packet then receiver needs to split, because Codec2 requires frames of specific size, it cannot decode complete packet containing multiple audio frames. It is possible to send packet containing one audio frame, but if such small packet is re-transmitted over e.g. KISS Bluetooth then there are delays, because Bluetooth is not efficient in operating on such stream of small packets.
Tested. I can't achieve sequential decoding without codec2 framing algorithm, all bytes is mixed up in queue and codec2 can't read it. Only first byte is present on its place - all another is completely mixed... very strange behaviour, maybe codec2 such works peculiarity...
For half-duplex, a.k.a. "parrot" you need to accumulate number of packets, wait for some time until no more data is received and then re-transmit complete buffer (this is how analog "parrots" are operating). So you need 2-2.5x channel time for each user's 1x transmission. If you want to re-transmit each packet immediately then sender (and other users) must know that he should wait for a packet duration, otherwise there will be interference, it requires some kind of time slot mechanism and you will need at least 2x bandwidth on the channel. Taking this complexity into account, it might be easier for repeater to operate full duplex with two modems and clients to operate half duplex with frequency split?
Half-duplex - is aka TDMA (or simpler TDM — Time Division Multiplexing) in digital world ;))
If we stay in "packet paradigm" we always has opportunity to slice it as we need. Of corse transmitting speed is doubled to be need, but this idea is workable for my opinion. Will test it...
TDMA is more about splitting unidirectional channel by time, here you will need to split bidirectional channel, probably, with some guard intervals and CSMA, like in WiFI, but unlike WiFi hidden nodes problem will be more dramatic when multiple users will be operating. It needs more complex protocol, which will eat a lot of narrow band channel bandwidth, so adding second 5$ modem for full duplex and uplink/downlink splitting might be an option.
For best understanding see my description of entire algorithm:
- Station A start transmitting - master station;
- Every period of time = 2*(time of coding/decoding and sending/receiving one packet +5% protection interval) master station send one packet;
- Station C - slave station (repeater):
3.1. Receive packet and control CRC (internal LORA modem function);
3.2. If packet CRC = OK - read from buffer and transmit (this process timing equal by described on step 2);
3.3. If packet CRC != OK - - trashed and waiting next packet;
3.4. Hearing next packet from master station. Because we did't send new packet from master station during time us formalised on step 2 we have time-slot on station C to process one packet; - Station B - slave station (recipient) - receive, control CRC, and buffering packet. Let's do such method for a couple of packets. The number of packets (N) which needed to buffering can be described us:
N = codec2 bitrate / packet length + 1 or 2 extra packets if we have loosed packets and need to continuously jobbing for codec2.
Next - codec2 read data from queue and play.
If station B need to callback to A - they just exchanging in described above scheme.
@Fotr0 , there might be need for clock synchronization between stations, otherwise there might be collision if A and B will transmit at the same time, for example, A and B can synchronize on first digirepeated packet from C or C can periodically send some beacon.
Yes, I thinking about it... Probably the better way like on SSFH synhronization to use GNSS lock-on. We need some 'tick' signal from any GNSS system.