initial commit

This commit is contained in:
2023-02-10 10:34:41 +01:00
commit fcad3a98b6
44 changed files with 1392 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
{
"build": {
"flags": "-Ofast"
}
}

View File

@@ -0,0 +1,30 @@
#include "DACOutput.h"
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
void DACOutput::start(int sample_rate)
{
// i2s config for writing both channels of I2S
i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN),
.sample_rate = sample_rate,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S_MSB),
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 2,
.dma_buf_len = 1024,
.use_apll = false,
.tx_desc_auto_clear = true,
.fixed_mclk = 0};
//install and start i2s driver
i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL);
// enable the DAC channels
i2s_set_dac_mode(I2S_DAC_CHANNEL_BOTH_EN);
// clear the DMA buffers
i2s_zero_dma_buffer(I2S_NUM_0);
i2s_start(I2S_NUM_0);
}
#endif

View File

@@ -0,0 +1,20 @@
#pragma once
#include <freertos/FreeRTOS.h>
#include <driver/i2s.h>
#include "Output.h"
/**
* Base Class for both the ADC and I2S sampler
**/
class DACOutput : public Output
{
public:
DACOutput(i2s_port_t i2s_port) : Output(i2s_port) {}
void start(int sample_rate);
virtual int16_t process_sample(int16_t sample)
{
// DAC needs unsigned 16 bit samples
return sample + 32768;
}
};

View File

@@ -0,0 +1,35 @@
#include "I2SOutput.h"
I2SOutput::I2SOutput(i2s_port_t i2s_port, i2s_pin_config_t &i2s_pins) : Output(i2s_port), m_i2s_pins(i2s_pins)
{
}
void I2SOutput::start(int sample_rate)
{
// i2s config for writing both channels of I2S
i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
.sample_rate = sample_rate,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0)
.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_STAND_I2S),
#else
.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S),
#endif
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 2,
.dma_buf_len = 1024,
.use_apll = false,
.tx_desc_auto_clear = true,
.fixed_mclk = 0};
//install and start i2s driver
i2s_driver_install(m_i2s_port, &i2s_config, 0, NULL);
// set up the i2s pins
i2s_set_pin(m_i2s_port, &m_i2s_pins);
// clear the DMA buffers
i2s_zero_dma_buffer(m_i2s_port);
i2s_start(m_i2s_port);
}

View File

@@ -0,0 +1,16 @@
#pragma once
#include "Output.h"
/**
* Base Class for both the ADC and I2S sampler
**/
class I2SOutput : public Output
{
private:
i2s_pin_config_t m_i2s_pins;
public:
I2SOutput(i2s_port_t i2s_port, i2s_pin_config_t &i2s_pins);
void start(int sample_rate);
};

View File

@@ -0,0 +1,51 @@
#include "Output.h"
#include <esp_log.h>
#include <driver/i2s.h>
static const char *TAG = "OUT";
// number of frames to try and send at once (a frame is a left and right sample)
const int NUM_FRAMES_TO_SEND = 256;
Output::Output(i2s_port_t i2s_port) : m_i2s_port(i2s_port)
{
// this will contain the prepared samples for sending to the I2S device
m_frames = (int16_t *)malloc(2 * sizeof(int16_t) * NUM_FRAMES_TO_SEND);
}
Output::~Output()
{
free(m_frames);
}
void Output::stop()
{
// stop the i2S driver
i2s_stop(m_i2s_port);
i2s_driver_uninstall(m_i2s_port);
}
void Output::write(int16_t *samples, int count)
{
int sample_index = 0;
while (sample_index < count)
{
int samples_to_send = 0;
for (int i = 0; i < NUM_FRAMES_TO_SEND && sample_index < count; i++)
{
int sample = process_sample(samples[sample_index]);
m_frames[i * 2] = sample; // left channel
m_frames[i * 2 + 1] = sample; // right channel
samples_to_send++;
sample_index++;
}
// write data to the i2s peripheral
size_t bytes_written = 0;
i2s_write(m_i2s_port, m_frames, samples_to_send * sizeof(int16_t) * 2, &bytes_written, portMAX_DELAY);
if (bytes_written != samples_to_send * sizeof(int16_t) * 2)
{
ESP_LOGE(TAG, "Did not write all bytes");
}
}
}

View File

@@ -0,0 +1,27 @@
#pragma once
#include <freertos/FreeRTOS.h>
#include <driver/i2s.h>
/**
* Base Class for both the DAC and I2S output
**/
class Output
{
private:
int16_t *m_frames;
protected:
i2s_port_t m_i2s_port = I2S_NUM_0;
public:
Output(i2s_port_t i2s_port);
~Output();
virtual void start(int sample_rate) = 0;
void stop();
// override this in derived classes to turn the sample into
// something the output device expects - for the default case
// this is simply a pass through
virtual int16_t process_sample(int16_t sample) { return sample; }
void write(int16_t *samples, int count);
};

View File

@@ -0,0 +1,99 @@
#pragma once
#include <Arduino.h>
#include <freertos/FreeRTOS.h>
/**
* @brief Circular buffer for 8 bit unsigned PCM samples
*
*/
class OutputBuffer
{
private:
// how many samples should we buffer before outputting data?
int m_number_samples_to_buffer;
// where are we reading from
int m_read_head;
// where are we writing to
int m_write_head;
// keep track of how many samples we have
int m_available_samples;
// the total size of the buffer
int m_buffer_size;
// are we currently buffering samples?
bool m_buffering;
// the sample buffer
uint8_t *m_buffer;
// thread safety
SemaphoreHandle_t m_semaphore;
public:
OutputBuffer(int number_samples_to_buffer) : m_number_samples_to_buffer(number_samples_to_buffer)
{
// create a semaphore and make it available for locking
m_semaphore = xSemaphoreCreateBinary();
xSemaphoreGive(m_semaphore);
// set reading and writing to the beginning of the buffer
m_read_head = 0;
m_write_head = 0;
m_available_samples = 0;
// we'll start off buffering data as we have no samples yet
m_buffering = true;
// make sufficient space for the bufferring and incoming data
m_buffer_size = 3 * number_samples_to_buffer;
m_buffer = (uint8_t *)malloc(m_buffer_size);
memset(m_buffer, 0, m_buffer_size);
if (!m_buffer)
{
Serial.println("Failed to allocate buffer");
}
}
// we're adding samples that are 8 bit as they are coming from the transport
void add_samples(const uint8_t *samples, int count)
{
xSemaphoreTake(m_semaphore, portMAX_DELAY);
// copy the samples into the buffer wrapping around as needed
for (int i = 0; i < count; i++)
{
m_buffer[m_write_head] = samples[i];
m_write_head = (m_write_head + 1) % m_buffer_size;
}
m_available_samples += count;
xSemaphoreGive(m_semaphore);
}
// convert the samples to 16 bit as they are going to the output
void remove_samples(int16_t *samples, int count)
{
xSemaphoreTake(m_semaphore, portMAX_DELAY);
for (int i = 0; i < count; i++)
{
samples[i] = 0;
// if we have no samples and we aren't already buffering then we need to start buffering
if (m_available_samples == 0 && !m_buffering)
{
Serial.println("Buffering");
m_buffering = true;
samples[i] = 0;
}
// are we buffering?
if (m_buffering && m_available_samples < m_number_samples_to_buffer)
{
// just return 0 as we don't have enough samples yet
samples[i] = 0;
}
else
{
// we've buffered enough samples so no need to buffer anymore
m_buffering = false;
// just send back the samples we've got and move the read head forward
int16_t sample = m_buffer[m_read_head];
samples[i] = (sample - 128) << 5;
m_read_head = (m_read_head + 1) % m_buffer_size;
m_available_samples--;
}
}
xSemaphoreGive(m_semaphore);
}
};