You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
187 lines
6.4 KiB
187 lines
6.4 KiB
/*
|
|
* i2s
|
|
*
|
|
* http://esp-idf.readthedocs.io/en/latest/api/peripherals/i2s.html
|
|
* https://github.com/espressif/esp-idf/blob/375b28650bd1c90d6ac706f63cde9a64d9a7e3e5/examples/peripherals/i2s/main/i2s_example_main.c
|
|
* see: https://github.com/MrBuddyCasino/ESP32_MP3_Decoder/blob/master/components/audio_renderer/audio_renderer.c
|
|
*/
|
|
|
|
#include "sound.h"
|
|
#include "datasource.h"
|
|
#include "sdcard.h"
|
|
|
|
#include "Arduino.h"
|
|
#include <stdio.h>
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
#include "driver/i2s.h"
|
|
//#include <soc/i2s_reg.h>
|
|
#include <math.h>
|
|
#include "freertos/semphr.h"
|
|
|
|
|
|
#define SAMPLE_RATE (11025)
|
|
#define WAVE_FREQ_HZ (100)
|
|
//#define PI 3.14159265
|
|
|
|
#define SAMPLE_PER_CYCLE (SAMPLE_RATE/WAVE_FREQ_HZ)
|
|
|
|
#define EXIT_BIT 0x01
|
|
|
|
using namespace std;
|
|
|
|
SemaphoreHandle_t xPlayingSemaphore = NULL;
|
|
static TaskHandle_t xTaskToNotify = NULL;
|
|
Sound* Sound::instance = NULL;
|
|
|
|
Sound::Sound() {
|
|
xPlayingSemaphore = xSemaphoreCreateMutex();
|
|
Sound::instance = this;
|
|
}
|
|
|
|
Sound* Sound::getInstance() {
|
|
return Sound::instance;
|
|
}
|
|
|
|
void Sound::init() {
|
|
unsigned int i, sample_val;
|
|
float sin_float, triangle_float, triangle_step = 65536.0 / SAMPLE_PER_CYCLE;
|
|
//for 36Khz sample rates, we create 100Hz sine wave, every cycle need 36000/100 = 360 samples (4-bytes each sample)
|
|
//using 6 buffers, we need 60-samples per buffer
|
|
//2-channels, 16-bit each channel, total buffer is 360*4 = 1440 bytes
|
|
i2s_config_t i2s_config;
|
|
i2s_config.mode = (i2s_mode_t) (I2S_MODE_MASTER | I2S_MODE_TX); // Only TX
|
|
i2s_config.sample_rate = SAMPLE_RATE;
|
|
i2s_config.bits_per_sample = (i2s_bits_per_sample_t)16; //16-bit per channel
|
|
i2s_config.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT; //2-channels
|
|
i2s_config.communication_format = (i2s_comm_format_t) (I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB);
|
|
i2s_config.dma_buf_count = 6;
|
|
i2s_config.dma_buf_len = 60; //
|
|
i2s_config.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1; //Interrupt level 1
|
|
|
|
i2s_pin_config_t pin_config = {
|
|
.bck_io_num = 26,
|
|
.ws_io_num = 25,
|
|
.data_out_num = 22,
|
|
.data_in_num = -1 //Not used
|
|
};
|
|
|
|
i2s_driver_install(i2s_num, &i2s_config, 0, NULL);
|
|
i2s_set_pin(i2s_num, &pin_config);
|
|
|
|
/* play a sine wave (example code) */
|
|
triangle_float = -32767;
|
|
|
|
for(i = 0; i < SAMPLE_PER_CYCLE; i++) {
|
|
sin_float = sin(i * PI / 180.0);
|
|
if(sin_float >= 0)
|
|
triangle_float += triangle_step;
|
|
else
|
|
triangle_float -= triangle_step;
|
|
sin_float *= 32767;
|
|
|
|
sample_val = 0;
|
|
sample_val += (short)triangle_float;
|
|
sample_val = sample_val << 16;
|
|
sample_val += (short) sin_float;
|
|
|
|
i2s_push_sample(i2s_num, (char *)&sample_val, portMAX_DELAY);
|
|
}
|
|
|
|
i2s_stop(i2s_num);
|
|
}
|
|
|
|
|
|
void Sound::end() {
|
|
i2s_driver_uninstall(i2s_num); //stop & destroy i2s driver
|
|
}
|
|
|
|
void Sound::play(const char* path) {
|
|
if (xTaskToNotify != NULL) {
|
|
xTaskNotify( xTaskToNotify,
|
|
EXIT_BIT,
|
|
eSetBits);
|
|
taskYIELD();
|
|
}
|
|
|
|
xTaskCreate(&(Sound::play_task), "play_task", 3072, (void*)path, 5, &xTaskSound);
|
|
}
|
|
|
|
void Sound::play_task(void *pvParameter) {
|
|
// Serial.print("initial free stack: "); Serial.println(uxTaskGetStackHighWaterMark( NULL ), DEC); // DEBUG
|
|
char *path = (char *) pvParameter;
|
|
Serial.println("sound task: playing");
|
|
if ( xPlayingSemaphore == NULL || xSemaphoreTake( xPlayingSemaphore, 42 / portTICK_PERIOD_MS) != pdTRUE ) {
|
|
Serial.println("Sound task: could not obtain semaphore");
|
|
vTaskDelete( NULL );
|
|
return;
|
|
}
|
|
|
|
xTaskToNotify = xTaskGetCurrentTaskHandle();
|
|
|
|
// Serial.print("before SDCard::open(); free stack: "); Serial.println(uxTaskGetStackHighWaterMark( NULL ), DEC); // DEBUG
|
|
|
|
File file = SDCard::open(path); // ToDo: accessing static member function loads 1500 words onto stack!
|
|
if (!file) {
|
|
Serial.print("Failed to open file: "); Serial.println(path);
|
|
xTaskToNotify = NULL;
|
|
xSemaphoreGive( xPlayingSemaphore );
|
|
vTaskDelete( NULL );
|
|
return;
|
|
}
|
|
|
|
// Serial.print("before new; free stack: "); Serial.println(uxTaskGetStackHighWaterMark( NULL ), DEC); // DEBUG
|
|
unsigned char* buf = new unsigned char[BUF_LENGTH];
|
|
unsigned int buf_pos;
|
|
Serial.print("Allocated buffer at: "); Serial.println((int)buf,HEX);
|
|
// Serial.print("after new; free stack: "); Serial.println(uxTaskGetStackHighWaterMark( NULL ), DEC); // DEBUG
|
|
|
|
i2s_start(i2s_num);
|
|
|
|
while (SDCard::available(file)) {
|
|
// Serial.print("chunk free stack: "); Serial.println(uxTaskGetStackHighWaterMark( NULL ), DEC); // DEBUG
|
|
// get chunk of data
|
|
SDCard::read(file, buf, BUF_LENGTH); // overwrite buffer
|
|
buf_pos = 0;
|
|
|
|
while (buf_pos < BUF_LENGTH) {
|
|
// Serial.print("i2s_push free stack: "); Serial.println(uxTaskGetStackHighWaterMark( NULL ), DEC); // DEBUG
|
|
uint16_t *buf16 = (uint16_t *) buf;
|
|
int buf16_pos = buf_pos/(sizeof(uint16_t)*2); // ToDo: read number of channels samplerate etc
|
|
// convert data
|
|
//unsigned int sample = ((unsigned short) DataSource::buffer[DataSource::buf_pos] << 16 & 0xffff0000) | ((unsigned short) DataSource::buffer[DataSource::buf_pos]);
|
|
unsigned int sample = ((unsigned short) buf16[buf16_pos] << 16 & 0xffff0000) | ((unsigned short) buf16[buf16_pos]);
|
|
// push samples
|
|
int num_pushed_bytes = i2s_push_sample(Sound::i2s_num, (char *)&sample, 0);
|
|
if (num_pushed_bytes == 0) {
|
|
Serial.println("i2s buf filled");
|
|
vTaskDelay(20 / portTICK_PERIOD_MS);
|
|
}
|
|
buf_pos += num_pushed_bytes;
|
|
}
|
|
// Buffer now empty
|
|
|
|
uint32_t ulNotifiedValue;
|
|
BaseType_t xResult = xTaskNotifyWait( pdFALSE, /* Don't clear bits on entry. */
|
|
ULONG_MAX, /* Clear all bits on exit. */
|
|
&ulNotifiedValue, /* Stores the notified value. */
|
|
0 ); /* xMaxBlockTime */
|
|
if( xResult == pdPASS ) { /* A notification was received */
|
|
if( ( ulNotifiedValue & EXIT_BIT ) != 0 ) { /* the EXIT BIT was set */
|
|
Serial.println("play_task: got notify. exiting.");
|
|
break;
|
|
}
|
|
}
|
|
|
|
//TickType_t delay = 10 / portTICK_PERIOD_MS; // max delay: 10ms instead of portMAX_DELAY
|
|
vTaskDelay(20 / portTICK_PERIOD_MS);
|
|
}
|
|
Serial.println("sdcard: EOF");
|
|
SDCard::close(file);
|
|
i2s_stop(i2s_num);
|
|
delete[] buf;
|
|
Serial.println("task exiting");
|
|
xTaskToNotify = NULL;
|
|
xSemaphoreGive( xPlayingSemaphore );
|
|
vTaskDelete( NULL );
|
|
}
|
|
|