esp-open-rtos/examples/i2s_audio/i2s_audio_example.c

197 lines
5.8 KiB
C

/* i2s_audio_example.c - Plays wav file from spiffs.
*
* This example demonstrates how to use I2S with DMA to output audio.
* The example is tested with TDA5143 16 bit DAC. But should work with
* any I2S DAC.
*
* The example reads a file with name "sample.wav" from the file system and
* feeds audio samples into DMA subsystem which outputs it into I2S bus.
* Currently only 44100 Hz 16 bit 2 channel audio is supported.
*
* In order to test this example you need to place a file with name "sample.wav"
* into directory "files". This file will be uploaded into spiffs on the device.
* The size of the sample file must be less than 1MB to fit into spiffs image.
* The format of the sample file must be 44100Hz, 16bit, 2 channels.
* Also you need a DAC connected to ESP8266 to convert I2S stream to analog
* output. Three wire must be connected: DATA, WS, CLOCK.
*
* This sample code is in the public domain.,
*/
#include "esp/uart.h"
#include "FreeRTOS.h"
#include "queue.h"
#include "task.h"
#include "esp8266.h"
#include "fcntl.h"
#include "unistd.h"
#include <stdio.h>
#include <stdint.h>
#include "esp_spiffs.h"
#include "i2s_dma/i2s_dma.h"
// Very simple WAV header, ignores most fields
typedef struct __attribute__((packed)) {
uint8_t ignore_0[22];
uint16_t num_channels;
uint32_t sample_rate;
uint8_t ignore_1[6];
uint16_t bits_per_sample;
uint8_t ignore_2[4];
uint32_t data_size;
uint8_t data[];
} dumb_wav_header_t;
// When samples are not sent fast enough underrun condition occurs
volatile uint32_t underrun_counter = 0;
#define DMA_BUFFER_SIZE 2048
#define DMA_QUEUE_SIZE 8
// Circular list of descriptors
static dma_descriptor_t dma_block_list[DMA_QUEUE_SIZE];
// Array of buffers for circular list of descriptors
static uint8_t dma_buffer[DMA_QUEUE_SIZE][DMA_BUFFER_SIZE];
// Queue of empty DMA blocks
static QueueHandle_t dma_queue;
/**
* Create a circular list of DMA descriptors
*/
static inline void init_descriptors_list()
{
memset(dma_buffer, 0, DMA_QUEUE_SIZE * DMA_BUFFER_SIZE);
for (int i = 0; i < DMA_QUEUE_SIZE; i++) {
dma_block_list[i].owner = 1;
dma_block_list[i].eof = 1;
dma_block_list[i].sub_sof = 0;
dma_block_list[i].unused = 0;
dma_block_list[i].buf_ptr = dma_buffer[i];
dma_block_list[i].datalen = DMA_BUFFER_SIZE;
dma_block_list[i].blocksize = DMA_BUFFER_SIZE;
if (i == (DMA_QUEUE_SIZE - 1)) {
dma_block_list[i].next_link_ptr = &dma_block_list[0];
} else {
dma_block_list[i].next_link_ptr = &dma_block_list[i + 1];
}
}
// The queue depth is one smaller than the amount of buffers we have,
// because there's always a buffer that is being used by the DMA subsystem
// *right now* and we don't want to be able to write to that simultaneously
dma_queue = xQueueCreate(DMA_QUEUE_SIZE - 1, sizeof(uint8_t*));
}
// DMA interrupt handler. It is called each time a DMA block is finished processing.
static void dma_isr_handler(void *args)
{
portBASE_TYPE task_awoken = pdFALSE;
if (i2s_dma_is_eof_interrupt()) {
dma_descriptor_t *descr = i2s_dma_get_eof_descriptor();
if (xQueueIsQueueFullFromISR(dma_queue)) {
// List of empty blocks is full. Sender don't send data fast enough.
int dummy;
underrun_counter++;
// Discard top of the queue
xQueueReceiveFromISR(dma_queue, &dummy, &task_awoken);
}
// Push the processed buffer to the queue so sender can refill it.
xQueueSendFromISR(dma_queue, (void*)(&descr->buf_ptr), &task_awoken);
}
i2s_dma_clear_interrupt();
portEND_SWITCHING_ISR(task_awoken);
}
static bool play_data(int fd)
{
uint8_t *curr_dma_buf;
// Get a free block from the DMA queue. This call will suspend the task
// until a free block is available in the queue.
if (xQueueReceive(dma_queue, &curr_dma_buf, portMAX_DELAY) == pdFALSE) {
// Or timeout occurs
printf("Cound't get free blocks to push data\n");
}
int read_bytes = read(fd, curr_dma_buf, DMA_BUFFER_SIZE);
if (read_bytes <= 0) {
return false;
}
return true;
}
void play_task(void *pvParameters)
{
esp_spiffs_init();
if (esp_spiffs_mount() != SPIFFS_OK) {
printf("Error mount SPIFFS\n");
}
int fd = open("sample.wav", O_RDONLY);
if (fd < 0) {
printf("Error opening file\n");
return;
}
dumb_wav_header_t wav_header;
read(fd, (void*)&wav_header, sizeof(wav_header));
printf("Number of channels: %d\n", wav_header.num_channels);
printf("Bits per sample: %d\n", wav_header.bits_per_sample);
printf("Sample rate: %d\n", wav_header.sample_rate);
printf("Data size: %d\n", wav_header.data_size);
if (wav_header.bits_per_sample != 16) {
printf("Only 16 bit per sample is supported\n");
return;
}
if (wav_header.num_channels != 2) {
printf("Only 2 channels is supported\n");
return;
}
i2s_clock_div_t clock_div = i2s_get_clock_div(
wav_header.sample_rate * 2 * 16);
printf("i2s clock dividers, bclk=%d, clkm=%d\n",
clock_div.bclk_div, clock_div.clkm_div);
i2s_pins_t i2s_pins = {.data = true, .clock = true, .ws = true};
i2s_dma_init(dma_isr_handler, NULL, clock_div, i2s_pins);
while (1) {
init_descriptors_list();
i2s_dma_start(dma_block_list);
lseek(fd, sizeof(dumb_wav_header_t), SEEK_SET);
while (play_data(fd)) {};
i2s_dma_stop();
vQueueDelete(dma_queue);
printf("underrun counter: %d\n", underrun_counter);
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
close(fd);
}
void user_init(void)
{
uart_set_baud(0, 115200);
xTaskCreate(play_task, "test_task", 1024, NULL, 2, NULL);
}