A2DP source Task buffer fails to fill the Queue the 100th time, every time. Why?
Posted: Tue Jan 24, 2023 1:14 am
I'm using ESP32 4MB flash at 240 MHz and have code based on the A2DP source example in ESP-IDF. Because I want to move to more flexibility between the BT audio callback calls, I have filled the sample buffer outside the BT callback in a Task and am passing the pointer to the buffer in a Queue. I'm using a button Task and ISR to send the buffer task the button state and when it is pressed, the buffer stops sending a pointer to a buffer full of zeroes and sends a pointer to a buffer full of audio samples which it reads from SPIFFS using fread().
The problem is that I hear a crackle in the audio for samples after around 300 milliseconds. I have checked and the BT callback is finding the sample pointer Queue empty at that point - either the 99th (first time) or 100th(every subsequent time) loop round the buffer filling task. The buffer is 512 samples, btw, as you'd expect - to match the BT A2DP buffer size. The strange thing is that even after disabling watchdogs or changing their durations (the interrupt watchdog was set at 300ms initially) the failed sample is always at around 300 ms after the button is pressed (the 100th sample / loop round the buffer filling routine). If I play a 2 second audio clip from SPIFFS, or a 500 ms clip, there is only ever a crackle / failure to fill the Queue for the 100th loop since the button was pressed in the buffer filling task.
Forgive my commenting out and elements of old code, I'm having to try all sorts of things to work out what might be causing the queue error at the 100th sample.
The problem is that I hear a crackle in the audio for samples after around 300 milliseconds. I have checked and the BT callback is finding the sample pointer Queue empty at that point - either the 99th (first time) or 100th(every subsequent time) loop round the buffer filling task. The buffer is 512 samples, btw, as you'd expect - to match the BT A2DP buffer size. The strange thing is that even after disabling watchdogs or changing their durations (the interrupt watchdog was set at 300ms initially) the failed sample is always at around 300 ms after the button is pressed (the 100th sample / loop round the buffer filling routine). If I play a 2 second audio clip from SPIFFS, or a 500 ms clip, there is only ever a crackle / failure to fill the Queue for the 100th loop since the button was pressed in the buffer filling task.
Forgive my commenting out and elements of old code, I'm having to try all sorts of things to work out what might be causing the queue error at the 100th sample.
- /*
- * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
- *
- * SPDX-License-Identifier: Unlicense OR CC0-1.0
- */
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <string.h>
- #include "freertos/FreeRTOS.h"
- #include "freertos/task.h"
- #include "freertos/timers.h"
- #include "nvs.h"
- #include "nvs_flash.h"
- #include "esp_system.h"
- #include "esp_log.h"
- #include "esp_bt.h"
- #include "bt_app_core.h"
- #include "esp_bt_main.h"
- #include "esp_bt_device.h"
- #include "esp_gap_bt_api.h"
- #include "esp_a2dp_api.h"
- #include "esp_avrc_api.h"
- // FreeRTOS
- #include "driver/gpio.h" // for input to test audio responsiveness
- #include "freertos/queue.h" // so input tasks can trigger audio etc
- #include "freertos/stream_buffer.h" // to pass mixed audio samples from the mixing task to the BT audio callback
- // For SPIFFS
- #include "esp_partition.h"
- #include "esp_spiffs.h"
- #include "esp_vfs.h"
- /* log tags */
- #define BT_AV_TAG "BT_AV"
- #define BT_RC_CT_TAG "RC_CT"
- #define APP_TAG "APP"
- /* device name */
- //#define TARGET_DEVICE_NAME "Aukey SK-M7"
- #define TARGET_DEVICE_NAME "Untangled Pro"
- #define LOCAL_DEVICE_NAME "ESP_A2DP_SRC"
- /* AVRCP used transaction label */
- #define APP_RC_CT_TL_GET_CAPS (0)
- #define APP_RC_CT_TL_RN_VOLUME_CHANGE (1)
- /* Pin name definitions*/
- #define BUTTON_PIN GPIO_NUM_27
- #define LED_PIN GPIO_NUM_2
- /* ISR Flag*/
- #define ESP_INR_FLAG_DEFAULT 0
- /* Whether to use SPIFFS or attached header file*/
- #define SPIFFS_SAMPLE
- #define SAMPLE_STREAM_BUFFER_SIZE 512
- enum {
- BT_APP_STACK_UP_EVT = 0x0000, /* event for stack up */
- BT_APP_HEART_BEAT_EVT = 0xff00, /* event for heart beat */
- };
- /* A2DP global states */
- enum {
- APP_AV_STATE_IDLE,
- APP_AV_STATE_DISCOVERING,
- APP_AV_STATE_DISCOVERED,
- APP_AV_STATE_UNCONNECTED,
- APP_AV_STATE_CONNECTING,
- APP_AV_STATE_CONNECTED,
- APP_AV_STATE_DISCONNECTING,
- };
- /* sub states of APP_AV_STATE_CONNECTED */
- enum {
- APP_AV_MEDIA_STATE_IDLE,
- APP_AV_MEDIA_STATE_STARTING,
- APP_AV_MEDIA_STATE_STARTED,
- APP_AV_MEDIA_STATE_STOPPING,
- };
- /* Holds pointer and number of samples to read for BT audio callback*/
- typedef struct
- {
- uint8_t * data_pointer;
- int number_of_bytes;
- int sample_count;
- } t_stAudioSample;
- /*********************************
- * STATIC FUNCTION DECLARATIONS
- ********************************/
- /* handler for bluetooth stack enabled events */
- static void bt_av_hdl_stack_evt(uint16_t event, void *p_param);
- /* avrc controller event handler */
- static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param);
- /* GAP callback function */
- static void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param);
- /* callback function for A2DP source */
- static void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param);
- /* callback function for A2DP source SPIFFS audio data stream using queues to decide when to play */
- static int32_t bt_app_a2d_spiffs_mono_sample_button_cb(uint8_t *data, int32_t len);
- /* callback function for A2DP source SPIFFS dual audio data stream using queues to decide when to play */
- static int32_t bt_app_a2d_spiffs_mixed_sample_button_cb(uint8_t *data, int32_t len);
- /* callback function for A2DP source SPIFFS dual audio data stream using queues to decide when to play */
- static int32_t bt_app_a2d_stream_buffer_button_cb(uint8_t *data, int32_t len);
- /* callback function for AVRCP controller */
- static void bt_app_rc_ct_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t *param);
- /* handler for heart beat timer */
- static void bt_app_a2d_heart_beat(TimerHandle_t arg);
- /* A2DP application state machine */
- static void bt_app_av_sm_hdlr(uint16_t event, void *param);
- /* utils for transfer BLuetooth Deveice Address into string form */
- static char *bda2str(esp_bd_addr_t bda, char *str, size_t size);
- /* A2DP application state machine handler for each state */
- static void bt_app_av_state_unconnected_hdlr(uint16_t event, void *param);
- static void bt_app_av_state_connecting_hdlr(uint16_t event, void *param);
- static void bt_app_av_state_connected_hdlr(uint16_t event, void *param);
- static void bt_app_av_state_disconnecting_hdlr(uint16_t event, void *param);
- /* initialise GPIOs for input/output*/
- static void app_gpio_init();
- /* ISR and ISR-called RTOS task*/
- /*static*/ void IRAM_ATTR button_isr_handler(void *arg);
- static void interrupt_task(void *arg);
- static void sample_mix_task(void *arg); // reads and mixes samples prior to transmission
- /*********************************
- * STATIC VARIABLE DEFINITIONS
- ********************************/
- static esp_bd_addr_t s_peer_bda = {0}; /* Bluetooth Device Address of peer device*/
- static uint8_t s_peer_bdname[ESP_BT_GAP_MAX_BDNAME_LEN + 1]; /* Bluetooth Device Name of peer device*/
- static int s_a2d_state = APP_AV_STATE_IDLE; /* A2DP global state */
- static int s_media_state = APP_AV_MEDIA_STATE_IDLE; /* sub states of APP_AV_STATE_CONNECTED */
- static int s_intv_cnt = 0; /* count of heart beat intervals */
- static int s_connecting_intv = 0; /* count of heart beat intervals for connecting */
- static uint32_t s_pkt_cnt = 0; /* count of packets */
- static esp_avrc_rn_evt_cap_mask_t s_avrc_peer_rn_cap; /* AVRC target notification event capability bit mask */
- static TimerHandle_t s_tmr; /* handle of heart beat timer */
- TaskHandle_t ISR = NULL; /* handle for button ISR */
- QueueHandle_t buttonQueue; /* handle for task data-sharing queue */
- TaskHandle_t SM_task = NULL; /* handle for button ISR */
- QueueHandle_t sampleBufferQueue; /* handle for task data-sharing queue */
- // For SPIFFs
- FILE* sample_file;
- FILE* sample_fileA;
- FILE* sample_fileB;
- FILE* sample_fileC;
- static const char *TAG = "SPIFFS";
- uint8_t mono_dataA[256] = {0};
- uint8_t mono_dataB[256] = {0};
- uint8_t sample_data[SAMPLE_STREAM_BUFFER_SIZE*2] = {0}; // initialise a test array of samples
- uint8_t blank_data[SAMPLE_STREAM_BUFFER_SIZE] = {0}; // silence samples for when there is no audio to send
- //static uint8_t sample_data[512]; // stores our sample data
- const size_t sampleStreamBufferSize = 512; // size of bytes
- const size_t sampleStreamTriggerNumber = 1; // allow stream to be read from if only 1 byte is present
- #define NUM_OF_VOLUME_SETTINGS 4
- uint8_t volumeDivisors[NUM_OF_VOLUME_SETTINGS] = {1, 2, 4, 8}; // what to divide sample magnitudes by for different volumes
- /*********************************
- * STATIC FUNCTION DEFINITIONS
- ********************************/
- static void init_spiffs()
- {
- ESP_LOGI(TAG, "Initializing SPIFFS");
- esp_vfs_spiffs_conf_t conf = {
- .base_path = "",
- .partition_label = NULL,
- .max_files = 5,
- .format_if_mount_failed = false
- };
- // Use settings defined above to initialize and mount SPIFFS filesystem.
- // Note: esp_vfs_spiffs_register is an all-in-one convenience function.
- esp_err_t ret = esp_vfs_spiffs_register(&conf);
- // Use POSIX and C standard library functions to work with files.
- // First create a file.
- // ESP_LOGI(TAG, "Opening file");
- // FILE* f = fopen("/spiffs/hello.txt", "w");
- // if (f == NULL) {
- // ESP_LOGE(TAG, "Failed to open file for writing");
- // return;
- // }
- // fprintf(f, "Hello World!\n");
- // fclose(f);
- // ESP_LOGI(TAG, "File written");
- if (ret != ESP_OK) {
- if (ret == ESP_FAIL) {
- ESP_LOGE(TAG, "Failed to mount or format filesystem");
- } else if (ret == ESP_ERR_NOT_FOUND) {
- ESP_LOGE(TAG, "Failed to find SPIFFS partition");
- } else {
- ESP_LOGE(TAG, "Failed to initialize SPIFFS (%s)", esp_err_to_name(ret));
- }
- return;
- }
- size_t total = 0, used = 0;
- ret = esp_spiffs_info(NULL, &total, &used);
- if (ret != ESP_OK) {
- ESP_LOGE(TAG, "Failed to get SPIFFS partition information (%s)", esp_err_to_name(ret));
- } else {
- ESP_LOGI(TAG, "Partition size: total: %d, used: %d", total, used);
- }
- }
- static void list_directory(const char* path)
- {
- printf("list_directory\n");
- DIR* dir = opendir(path);
- while (true) {
- struct dirent* de = readdir(dir);
- if (!de) {
- break;
- }
- printf("file found: %s\n", de->d_name);
- char pathname[1024]; /* should alwys be big enough */
- sprintf( pathname, "/%s", de->d_name );
- printf("pathname: %s\n", pathname);
- FILE* f = fopen(pathname, "r");
- if (f == NULL) {
- printf("could not open: %s\n", de->d_name);
- } else {
- printf("could open: %s\n", de->d_name);
- //char buf[32] = { 0 };
- //int cb = fread(buf, 1, sizeof(buf), f);
- fclose(f);
- };
- }
- closedir(dir);
- }
- static char *bda2str(esp_bd_addr_t bda, char *str, size_t size)
- {
- if (bda == NULL || str == NULL || size < 18) {
- return NULL;
- }
- sprintf(str, "%02x:%02x:%02x:%02x:%02x:%02x",
- bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]);
- return str;
- }
- static bool get_name_from_eir(uint8_t *eir, uint8_t *bdname, uint8_t *bdname_len)
- {
- uint8_t *rmt_bdname = NULL;
- uint8_t rmt_bdname_len = 0;
- if (!eir) {
- return false;
- }
- /* get complete or short local name from eir data */
- rmt_bdname = esp_bt_gap_resolve_eir_data(eir, ESP_BT_EIR_TYPE_CMPL_LOCAL_NAME, &rmt_bdname_len);
- if (!rmt_bdname) {
- rmt_bdname = esp_bt_gap_resolve_eir_data(eir, ESP_BT_EIR_TYPE_SHORT_LOCAL_NAME, &rmt_bdname_len);
- }
- if (rmt_bdname) {
- if (rmt_bdname_len > ESP_BT_GAP_MAX_BDNAME_LEN) {
- rmt_bdname_len = ESP_BT_GAP_MAX_BDNAME_LEN;
- }
- if (bdname) {
- memcpy(bdname, rmt_bdname, rmt_bdname_len);
- bdname[rmt_bdname_len] = '\0';
- }
- if (bdname_len) {
- *bdname_len = rmt_bdname_len;
- }
- return true;
- }
- return false;
- }
- static void filter_inquiry_scan_result(esp_bt_gap_cb_param_t *param)
- {
- char bda_str[18];
- uint32_t cod = 0; /* class of device */
- int32_t rssi = -129; /* invalid value */
- uint8_t *eir = NULL;
- esp_bt_gap_dev_prop_t *p;
- /* handle the discovery results */
- ESP_LOGI(BT_AV_TAG, "Scanned device: %s", bda2str(param->disc_res.bda, bda_str, 18));
- for (int i = 0; i < param->disc_res.num_prop; i++) {
- p = param->disc_res.prop + i;
- switch (p->type) {
- case ESP_BT_GAP_DEV_PROP_COD:
- cod = *(uint32_t *)(p->val);
- ESP_LOGI(BT_AV_TAG, "--Class of Device: 0x%x", cod);
- break;
- case ESP_BT_GAP_DEV_PROP_RSSI:
- rssi = *(int8_t *)(p->val);
- ESP_LOGI(BT_AV_TAG, "--RSSI: %d", rssi);
- break;
- case ESP_BT_GAP_DEV_PROP_EIR:
- eir = (uint8_t *)(p->val);
- ESP_LOGI(BT_AV_TAG, "--EIR: %d", eir);
- break;
- case ESP_BT_GAP_DEV_PROP_BDNAME:
- default:
- break;
- }
- }
- /* search for device with MAJOR service class as "rendering" in COD */
- if (!esp_bt_gap_is_valid_cod(cod) ||
- !(esp_bt_gap_get_cod_srvc(cod) & ESP_BT_COD_SRVC_RENDERING) ) {
- return;
- }
- /* search for target device in its Extended Inqury Response */
- if (eir) {
- get_name_from_eir(eir, s_peer_bdname, NULL);
- ESP_LOGI(BT_AV_TAG, "Found a rendering device name %s", s_peer_bdname);
- if (strcmp((char *)s_peer_bdname, TARGET_DEVICE_NAME) == 0) {
- ESP_LOGI(BT_AV_TAG, "Found a target device, address %s, name %s", bda_str, s_peer_bdname);
- s_a2d_state = APP_AV_STATE_DISCOVERED;
- memcpy(s_peer_bda, param->disc_res.bda, ESP_BD_ADDR_LEN); // s_peer_bda will now hold the BT address we want to connect to
- ESP_LOGI(BT_AV_TAG, "Cancel device discovery ...");
- esp_bt_gap_cancel_discovery();
- }
- }
- }
- static void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param)
- {
- switch (event) {
- /* when device discovered a result, this event comes */
- case ESP_BT_GAP_DISC_RES_EVT: {
- if (s_a2d_state == APP_AV_STATE_DISCOVERING) {
- filter_inquiry_scan_result(param);
- }
- break;
- }
- /* when discovery state changed, this event comes */
- case ESP_BT_GAP_DISC_STATE_CHANGED_EVT: {
- if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STOPPED) {
- if (s_a2d_state == APP_AV_STATE_DISCOVERED) {
- s_a2d_state = APP_AV_STATE_CONNECTING;
- ESP_LOGI(BT_AV_TAG, "Device discovery stopped.");
- ESP_LOGI(BT_AV_TAG, "a2dp connecting to peer: %s", s_peer_bdname);
- /* connect source to peer device specificed by Bluetooth Device Address */
- esp_a2d_source_connect(s_peer_bda);
- } else {
- /* not discovered, continue to discover */
- ESP_LOGI(BT_AV_TAG, "Device discovery failed, continue to discover...");
- esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0);
- }
- } else if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STARTED) {
- ESP_LOGI(BT_AV_TAG, "Discovery started.");
- }
- break;
- }
- /* when authentication completed, this event comes */
- case ESP_BT_GAP_AUTH_CMPL_EVT: {
- if (param->auth_cmpl.stat == ESP_BT_STATUS_SUCCESS) {
- ESP_LOGI(BT_AV_TAG, "authentication success: %s", param->auth_cmpl.device_name);
- esp_log_buffer_hex(BT_AV_TAG, param->auth_cmpl.bda, ESP_BD_ADDR_LEN);
- } else {
- ESP_LOGE(BT_AV_TAG, "authentication failed, status: %d", param->auth_cmpl.stat);
- }
- break;
- }
- /* when Legacy Pairing pin code requested, this event comes */
- case ESP_BT_GAP_PIN_REQ_EVT: {
- ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_PIN_REQ_EVT min_16_digit: %d", param->pin_req.min_16_digit);
- if (param->pin_req.min_16_digit) {
- ESP_LOGI(BT_AV_TAG, "Input pin code: 0000 0000 0000 0000");
- esp_bt_pin_code_t pin_code = {0};
- esp_bt_gap_pin_reply(param->pin_req.bda, true, 16, pin_code);
- } else {
- ESP_LOGI(BT_AV_TAG, "Input pin code: 1234");
- esp_bt_pin_code_t pin_code;
- pin_code[0] = '1';
- pin_code[1] = '2';
- pin_code[2] = '3';
- pin_code[3] = '4';
- esp_bt_gap_pin_reply(param->pin_req.bda, true, 4, pin_code);
- }
- break;
- }
- #if (CONFIG_BT_SSP_ENABLED == true)
- /* when Security Simple Pairing user confirmation requested, this event comes */
- case ESP_BT_GAP_CFM_REQ_EVT:
- ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_CFM_REQ_EVT Please compare the numeric value: %d", param->cfm_req.num_val);
- esp_bt_gap_ssp_confirm_reply(param->cfm_req.bda, true);
- break;
- /* when Security Simple Pairing passkey notified, this event comes */
- case ESP_BT_GAP_KEY_NOTIF_EVT:
- ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_KEY_NOTIF_EVT passkey: %d", param->key_notif.passkey);
- break;
- /* when Security Simple Pairing passkey requested, this event comes */
- case ESP_BT_GAP_KEY_REQ_EVT:
- ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_KEY_REQ_EVT Please enter passkey!");
- break;
- #endif
- /* when GAP mode changed, this event comes */
- case ESP_BT_GAP_MODE_CHG_EVT:
- ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_MODE_CHG_EVT mode: %d", param->mode_chg.mode);
- break;
- /* other */
- default: {
- ESP_LOGI(BT_AV_TAG, "event: %d", event);
- break;
- }
- }
- return;
- }
- static void bt_av_hdl_stack_evt(uint16_t event, void *p_param)
- {
- ESP_LOGD(BT_AV_TAG, "%s event: %d", __func__, event);
- switch (event) {
- /* when stack up worked, this event comes */
- case BT_APP_STACK_UP_EVT: {
- char *dev_name = LOCAL_DEVICE_NAME;
- esp_bt_dev_set_device_name(dev_name);
- esp_bt_gap_register_callback(bt_app_gap_cb);
- esp_avrc_ct_init();
- esp_avrc_ct_register_callback(bt_app_rc_ct_cb);
- esp_avrc_rn_evt_cap_mask_t evt_set = {0};
- esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_SET, &evt_set, ESP_AVRC_RN_VOLUME_CHANGE);
- ESP_ERROR_CHECK(esp_avrc_tg_set_rn_evt_cap(&evt_set));
- esp_a2d_source_init();
- esp_a2d_register_callback(&bt_app_a2d_cb);
- #ifdef SPIFFS_SAMPLE
- esp_a2d_source_register_data_callback(bt_app_a2d_stream_buffer_button_cb); // Where to source the audio data from
- #else
- // esp_a2d_source_register_data_callback(bt_app_a2d_snare_button_cb); // Where to source the audio data from
- #endif // SPIFFS_SAMPLE
- esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, /*ESP_BT_NON_DISCOVERABLE*/ ESP_BT_GENERAL_DISCOVERABLE);
- ESP_LOGI(BT_AV_TAG, "Starting device discovery...");
- s_a2d_state = APP_AV_STATE_DISCOVERING;
- esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0);
- /* create and start heart beat timer */
- do {
- int tmr_id = 0;
- s_tmr = xTimerCreate("connTmr", (10000 / portTICK_PERIOD_MS),
- pdTRUE, (void *) &tmr_id, bt_app_a2d_heart_beat);
- xTimerStart(s_tmr, portMAX_DELAY);
- } while (0);
- break;
- }
- /* other */
- default: {
- ESP_LOGE(BT_AV_TAG, "%s unhandled event: %d", __func__, event);
- break;
- }
- }
- }
- static void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param)
- {
- bt_app_work_dispatch(bt_app_av_sm_hdlr, event, param, sizeof(esp_a2d_cb_param_t), NULL);
- }
- /* read source audio from SPIFFS .raw sample files when we have been notified of a button press*/
- static int32_t bt_app_a2d_spiffs_mono_sample_button_cb(uint8_t *data, int32_t len)
- {
- static bool button_pressed = false;
- if( xQueueReceive(buttonQueue, &button_pressed, (TickType_t)0))
- {
- sample_file = sample_file == sample_fileA ? sample_fileB : sample_fileA; // alternate between samples
- ESP_LOGI(APP_TAG, "BT cb rxd message %d\n", button_pressed);
- fseek(sample_file, 0, SEEK_SET); // reset to play from beginning of sample to allow multiple hits to be played
- }
- if (data == NULL || len < 0) {
- return 0;
- }
- if (sample_file == NULL) {
- ESP_LOGE(BT_AV_TAG, "callback says sample file is not open");
- return 0;
- }
- if(button_pressed )
- {
- uint8_t mono_data[len/2];
- int l = fread(mono_data, 1, len/2, sample_file);
- /* Method for converting mono sample to stereo*/
- for (int i = 0; i < (len >> 1); i += 2) {
- // fill two samples with duplicate data to get stereo from mono sample in a normal time/duration
- // data[(i << 1) ] = mono_data[i];
- data[(i << 1) + 1] = mono_data[i + 1];
- // data[((i+1) << 1) ] = mono_data[i];
- data[((i+1) << 1) + 1] = mono_data[i + 1];
- }
- // Deal with end of file
- if (l < len/2) {
- fseek(sample_file, 0, SEEK_SET); // reset the file pointer to the beginning for next play
- button_pressed = false; // stop playing at the end of the sample
- int32_t difference = len - 2 * l;
- // ESP_LOGI(BT_AV_TAG,"sample short by %i ", difference);
- // Pad the buffer with silence
- int i = 0;
- for (i = l; i < (len >> 1); i++) {
- data[(i << 1) ] = 0;
- data[(i << 1) + 1] = 0;
- }
- }
- }
- else // need to stream silence if no audio
- {
- for (int i = 0; i < (len >> 1); i++) {
- data[(i << 1) ] = 0;
- data[(i << 1) + 1] = 0;
- }
- }
- return len;
- }
- /* read source audio from SPIFFS .raw sample files when we have been notified of a button press, and splice them together*/
- static int32_t bt_app_a2d_spiffs_mixed_sample_button_cb(uint8_t *data, int32_t len)
- {
- static bool button_pressed = false;
- static bool run_sampleA = false;
- static bool run_sampleB = false;
- if( xQueueReceive(buttonQueue, &button_pressed, (TickType_t)0))
- {
- //sample_file = sample_file == sample_fileA ? sample_fileB : sample_fileA; // alternate between samples
- //ESP_LOGI(APP_TAG, "BT cb rxd message %d\n", button_pressed);
- fseek(sample_fileA, 0, SEEK_SET); // reset to play from beginning of sample to allow multiple hits to be played
- fseek(sample_fileB, 0, SEEK_SET); // reset to play from beginning of sample to allow multiple hits to be played
- //run_sampleA = true;
- //run_sampleB = true;
- }
- if (data == NULL || len < 0) {
- return 0;
- }
- if (sample_fileA == NULL || sample_fileB == NULL) { // TODO: make null file handling for sample A and B graceful too
- ESP_LOGE(BT_AV_TAG, "callback says at least one sample file is not open");
- return 0;
- }
- if(button_pressed )
- {
- // uint8_t mono_dataA[len >> 1] ;//= {0};
- // uint8_t mono_dataB[len >> 1] ;// = {0};
- int lA = 0;
- int lB = 0;
- if(run_sampleA)
- {
- lA = fread(mono_dataA, 1, len/2, sample_fileA);
- if (lA < len/2) {
- fseek(sample_fileA, 0, SEEK_SET); // reset the file pointer to the beginning for next play
- run_sampleA = false; // stop playing at the end of the sample
- // int32_t difference = len - 2 * l;
- // ESP_LOGI(BT_AV_TAG,"sample short by %i ", difference);
- // Pad the buffer with silence
- int i = 0;
- for (i = lA; i < (len >> 1); i += 2) {
- mono_dataA[(i << 1) ] = 0;
- mono_dataA[(i << 1) + 1] = 0;
- }
- }
- }
- else
- {
- // for (int i = 0; i < (len >> 1); i += 2) {
- // mono_dataA[(i << 1) ] = 0;
- // mono_dataA[(i << 1) + 1] = 0;
- // }
- }
- /*
- if(run_sampleB)
- {
- lB = fread(mono_dataB, 1, len/2, sample_fileB);
- if (lB < len/2) {
- fseek(sample_fileB, 0, SEEK_SET); // reset the file pointer to the beginning for next play
- run_sampleB = false; // stop playing at the end of the sample
- // int32_t difference = len - 2 * l;
- // ESP_LOGI(BT_AV_TAG,"sample short by %i ", difference);
- // Pad the buffer with silence
- int i = 0;
- for (i = lB; i < (len >> 1); i += 2) {
- mono_dataB[(i << 1) ] = 0;
- mono_dataB[(i << 1) + 1] = 0;
- }
- }
- }
- else
- {
- for (int i = 0; i < (len >> 1); i += 2) {
- mono_dataB[(i << 1) ] = 0;
- mono_dataB[(i << 1) + 1] = 0;
- }
- }
- */
- /* Method for converting mono sample to stereo*/
- for (int i = 0; i < (len >> 1); i += 2) {
- // fill two samples with duplicate data to get stereo from mono sample in a normal time/duration
- // data[(i << 1) ] = (mono_dataA[i] >> 1) + (mono_dataB[i] >> 1);
- // data[(i << 1) + 1] = (mono_dataA[i + 1] >> 1) + (mono_dataB[i + 1] >> 1);
- // data[((i+1) << 1) ] = (mono_dataA[i] >> 1) + (mono_dataB[i] >> 1);
- // data[((i+1) << 1) + 1] = (mono_dataA[i + 1] >> 1) + (mono_dataB[i + 1] >> 1);
- data[(i << 1) ] = mono_dataA[i];
- data[(i << 1) + 1] = mono_dataA[i + 1];
- data[((i+1) << 1) ] = mono_dataA[i] ;
- data[((i+1) << 1) + 1] = mono_dataA[i + 1] ;
- }
- // Deal with end of file
- // TODO: make this work properly, so end of shorter file doesn't hinder longer file
- if (run_sampleA == false /*&& run_sampleB == false*/) {
- button_pressed = false;
- }
- }
- else // need to stream silence if no audio
- {
- for (int i = 0; i < (len >> 1); i++) {
- data[(i << 1) ] = 0;
- data[(i << 1) + 1] = 0;
- }
- }
- return len;
- }
- /* read source audio from a FreeRTOS stream buffer when we have been notified of a button press*/
- static int32_t bt_app_a2d_stream_buffer_button_cb(uint8_t *data, int32_t len)
- {
- static t_stAudioSample audio_clip;
- if (data == NULL || len < 0) {
- return 0;
- }
- //ESP_LOGI(APP_TAG, "BT cb len asked from buffer %d", len);
- if (xQueueReceive(sampleBufferQueue, &audio_clip, (TickType_t )0))
- {
- // memcpy(data, audio_clip.data_pointer, audio_clip.number_of_bytes);
- for(int i = 0; i < audio_clip.number_of_bytes; i++)
- {
- data[i] = *(audio_clip.data_pointer + i);
- }
- len = audio_clip.number_of_bytes;
- }
- else
- {
- for(int i = 0; i < len; i++)
- {
- data[i] = 0;
- }
- ESP_LOGI(APP_TAG, "S: %d", audio_clip.sample_count);
- }
- // if(len < 512)
- // ESP_LOGI(APP_TAG, "BT cb len read from buffer %d", len);
- xTaskNotifyGive( SM_task ); // direct to task messaging
- return len;
- }
- static void bt_app_a2d_heart_beat(TimerHandle_t arg)
- {
- bt_app_work_dispatch(bt_app_av_sm_hdlr, BT_APP_HEART_BEAT_EVT, NULL, 0, NULL);
- }
- static void bt_app_av_sm_hdlr(uint16_t event, void *param)
- {
- ESP_LOGI(BT_AV_TAG, "%s state: %d, event: 0x%x", __func__, s_a2d_state, event);
- /* select handler according to different states */
- switch (s_a2d_state) {
- case APP_AV_STATE_DISCOVERING:
- case APP_AV_STATE_DISCOVERED:
- break;
- case APP_AV_STATE_UNCONNECTED:
- bt_app_av_state_unconnected_hdlr(event, param);
- break;
- case APP_AV_STATE_CONNECTING:
- bt_app_av_state_connecting_hdlr(event, param);
- break;
- case APP_AV_STATE_CONNECTED:
- bt_app_av_state_connected_hdlr(event, param);
- break;
- case APP_AV_STATE_DISCONNECTING:
- bt_app_av_state_disconnecting_hdlr(event, param);
- break;
- default:
- ESP_LOGE(BT_AV_TAG, "%s invalid state: %d", __func__, s_a2d_state);
- break;
- }
- }
- static void bt_app_av_state_unconnected_hdlr(uint16_t event, void *param)
- {
- esp_a2d_cb_param_t *a2d = NULL;
- /* handle the events of intrest in unconnected state */
- switch (event) {
- case ESP_A2D_CONNECTION_STATE_EVT:
- case ESP_A2D_AUDIO_STATE_EVT:
- case ESP_A2D_AUDIO_CFG_EVT:
- case ESP_A2D_MEDIA_CTRL_ACK_EVT:
- break;
- case BT_APP_HEART_BEAT_EVT: {
- uint8_t *bda = s_peer_bda;
- ESP_LOGI(BT_AV_TAG, "a2dp connecting to peer address: %02x:%02x:%02x:%02x:%02x:%02x",
- bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]);
- ESP_LOGI(BT_AV_TAG, "a2dp connecting to peer named: %s", s_peer_bdname);
- esp_a2d_source_connect(s_peer_bda);
- s_a2d_state = APP_AV_STATE_CONNECTING;
- s_connecting_intv = 0;
- break;
- }
- case ESP_A2D_REPORT_SNK_DELAY_VALUE_EVT: {
- a2d = (esp_a2d_cb_param_t *)(param);
- ESP_LOGI(BT_AV_TAG, "%s, delay value: %u * 1/10 ms", __func__, a2d->a2d_report_delay_value_stat.delay_value);
- break;
- }
- default: {
- ESP_LOGE(BT_AV_TAG, "%s unhandled event: %d", __func__, event);
- break;
- }
- }
- }
- static void bt_app_av_state_connecting_hdlr(uint16_t event, void *param)
- {
- esp_a2d_cb_param_t *a2d = NULL;
- /* handle the events of intrest in connecting state */
- switch (event) {
- case ESP_A2D_CONNECTION_STATE_EVT: {
- a2d = (esp_a2d_cb_param_t *)(param);
- if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_CONNECTED) {
- ESP_LOGI(BT_AV_TAG, "a2dp connected");
- s_a2d_state = APP_AV_STATE_CONNECTED;
- s_media_state = APP_AV_MEDIA_STATE_IDLE;
- esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE);
- } else if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) {
- s_a2d_state = APP_AV_STATE_UNCONNECTED;
- }
- break;
- }
- case ESP_A2D_AUDIO_STATE_EVT:
- case ESP_A2D_AUDIO_CFG_EVT:
- case ESP_A2D_MEDIA_CTRL_ACK_EVT:
- break;
- case BT_APP_HEART_BEAT_EVT:
- /* Try reconnecting twice if disconnected*/
- if (s_connecting_intv < 2)
- {
- ESP_LOGI(BT_AV_TAG, "a2dp connecting to peer named: %s", s_peer_bdname);
- esp_a2d_source_connect(s_peer_bda);
- }
- /**
- * Switch state to APP_AV_STATE_UNCONNECTED
- * when connecting lasts more than 2 heart beat intervals.
- */
- if (++s_connecting_intv >= 2) {
- s_a2d_state = APP_AV_STATE_UNCONNECTED;
- s_connecting_intv = 0;
- }
- break;
- case ESP_A2D_REPORT_SNK_DELAY_VALUE_EVT: {
- a2d = (esp_a2d_cb_param_t *)(param);
- ESP_LOGI(BT_AV_TAG, "%s, delay value: %u * 1/10 ms", __func__, a2d->a2d_report_delay_value_stat.delay_value);
- break;
- }
- default:
- ESP_LOGE(BT_AV_TAG, "%s unhandled event: %d", __func__, event);
- break;
- }
- }
- static void bt_app_av_media_proc(uint16_t event, void *param)
- {
- esp_a2d_cb_param_t *a2d = NULL;
- switch (s_media_state) {
- case APP_AV_MEDIA_STATE_IDLE: {
- if (event == BT_APP_HEART_BEAT_EVT) {
- ESP_LOGI(BT_AV_TAG, "a2dp media ready checking ...");
- esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_CHECK_SRC_RDY);
- } else if (event == ESP_A2D_MEDIA_CTRL_ACK_EVT) {
- a2d = (esp_a2d_cb_param_t *)(param);
- if (a2d->media_ctrl_stat.cmd == ESP_A2D_MEDIA_CTRL_CHECK_SRC_RDY &&
- a2d->media_ctrl_stat.status == ESP_A2D_MEDIA_CTRL_ACK_SUCCESS) {
- ESP_LOGI(BT_AV_TAG, "a2dp media ready, starting ...");
- esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_START);
- s_media_state = APP_AV_MEDIA_STATE_STARTING;
- }
- }
- break;
- }
- case APP_AV_MEDIA_STATE_STARTING: {
- if (event == ESP_A2D_MEDIA_CTRL_ACK_EVT) {
- a2d = (esp_a2d_cb_param_t *)(param);
- if (a2d->media_ctrl_stat.cmd == ESP_A2D_MEDIA_CTRL_START &&
- a2d->media_ctrl_stat.status == ESP_A2D_MEDIA_CTRL_ACK_SUCCESS) {
- ESP_LOGI(BT_AV_TAG, "a2dp media start successfully.");
- s_intv_cnt = 0;
- s_media_state = APP_AV_MEDIA_STATE_STARTED;
- } else {
- /* not started succesfully, transfer to idle state */
- ESP_LOGI(BT_AV_TAG, "a2dp media start failed.");
- s_media_state = APP_AV_MEDIA_STATE_IDLE;
- }
- }
- break;
- }
- case APP_AV_MEDIA_STATE_STARTED: {
- if (event == BT_APP_HEART_BEAT_EVT) {
- /* stop media after 10 heart beat intervals */
- if (++s_intv_cnt >= 10) {
- /*Comment out the automatic stopping every 10 heartbeats*/
- // ESP_LOGI(BT_AV_TAG, "a2dp media stopping...");
- // esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_STOP);
- // s_media_state = APP_AV_MEDIA_STATE_STOPPING;
- s_intv_cnt = 0;
- }
- }
- break;
- }
- case APP_AV_MEDIA_STATE_STOPPING: {
- if (event == ESP_A2D_MEDIA_CTRL_ACK_EVT) {
- a2d = (esp_a2d_cb_param_t *)(param);
- if (a2d->media_ctrl_stat.cmd == ESP_A2D_MEDIA_CTRL_STOP &&
- a2d->media_ctrl_stat.status == ESP_A2D_MEDIA_CTRL_ACK_SUCCESS) {
- ESP_LOGI(BT_AV_TAG, "a2dp media stopped successfully, disconnecting...");
- s_media_state = APP_AV_MEDIA_STATE_IDLE;
- esp_a2d_source_disconnect(s_peer_bda);
- s_a2d_state = APP_AV_STATE_DISCONNECTING;
- } else {
- ESP_LOGI(BT_AV_TAG, "a2dp media stopping...");
- esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_STOP);
- }
- }
- break;
- }
- default: {
- break;
- }
- }
- }
- static void bt_app_av_state_connected_hdlr(uint16_t event, void *param)
- {
- esp_a2d_cb_param_t *a2d = NULL;
- /* handle the events of interest in connected state */
- switch (event) {
- case ESP_A2D_CONNECTION_STATE_EVT: {
- a2d = (esp_a2d_cb_param_t *)(param);
- if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) {
- ESP_LOGI(BT_AV_TAG, "a2dp disconnected");
- s_a2d_state = APP_AV_STATE_UNCONNECTED;
- esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_NON_DISCOVERABLE/*ESP_BT_GENERAL_DISCOVERABLE*/); // DEBUG: attempt to include existing headphones in scan
- }
- break;
- }
- case ESP_A2D_AUDIO_STATE_EVT: {
- a2d = (esp_a2d_cb_param_t *)(param);
- if (ESP_A2D_AUDIO_STATE_STARTED == a2d->audio_stat.state) {
- s_pkt_cnt = 0;
- }
- break;
- }
- case ESP_A2D_AUDIO_CFG_EVT:
- // not suppposed to occur for A2DP source
- break;
- case ESP_A2D_MEDIA_CTRL_ACK_EVT:
- case BT_APP_HEART_BEAT_EVT: {
- bt_app_av_media_proc(event, param);
- break;
- }
- case ESP_A2D_REPORT_SNK_DELAY_VALUE_EVT: {
- a2d = (esp_a2d_cb_param_t *)(param);
- ESP_LOGI(BT_AV_TAG, "%s, delay value: %u * 1/10 ms", __func__, a2d->a2d_report_delay_value_stat.delay_value);
- break;
- }
- default: {
- ESP_LOGE(BT_AV_TAG, "%s unhandled event: %d", __func__, event);
- break;
- }
- }
- }
- static void bt_app_av_state_disconnecting_hdlr(uint16_t event, void *param)
- {
- esp_a2d_cb_param_t *a2d = NULL;
- /* handle the events of intrest in disconnecing state */
- switch (event) {
- case ESP_A2D_CONNECTION_STATE_EVT: {
- a2d = (esp_a2d_cb_param_t *)(param);
- if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) {
- ESP_LOGI(BT_AV_TAG, "a2dp disconnected");
- s_a2d_state = APP_AV_STATE_UNCONNECTED;
- esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);
- }
- break;
- }
- case ESP_A2D_AUDIO_STATE_EVT:
- case ESP_A2D_AUDIO_CFG_EVT:
- case ESP_A2D_MEDIA_CTRL_ACK_EVT:
- case BT_APP_HEART_BEAT_EVT:
- break;
- case ESP_A2D_REPORT_SNK_DELAY_VALUE_EVT: {
- a2d = (esp_a2d_cb_param_t *)(param);
- ESP_LOGI(BT_AV_TAG, "%s, delay value: 0x%u * 1/10 ms", __func__, a2d->a2d_report_delay_value_stat.delay_value);
- break;
- }
- default: {
- ESP_LOGE(BT_AV_TAG, "%s unhandled event: %d", __func__, event);
- break;
- }
- }
- }
- /* callback function for AVRCP controller */
- static void bt_app_rc_ct_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t *param)
- {
- switch (event) {
- case ESP_AVRC_CT_CONNECTION_STATE_EVT:
- case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT:
- case ESP_AVRC_CT_METADATA_RSP_EVT:
- case ESP_AVRC_CT_CHANGE_NOTIFY_EVT:
- case ESP_AVRC_CT_REMOTE_FEATURES_EVT:
- case ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT:
- case ESP_AVRC_CT_SET_ABSOLUTE_VOLUME_RSP_EVT: {
- bt_app_work_dispatch(bt_av_hdl_avrc_ct_evt, event, param, sizeof(esp_avrc_ct_cb_param_t), NULL);
- break;
- }
- default: {
- ESP_LOGE(BT_RC_CT_TAG, "Invalid AVRC event: %d", event);
- break;
- }
- }
- }
- static void bt_av_volume_changed(void)
- {
- if (esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_TEST, &s_avrc_peer_rn_cap,
- ESP_AVRC_RN_VOLUME_CHANGE)) {
- esp_avrc_ct_send_register_notification_cmd(APP_RC_CT_TL_RN_VOLUME_CHANGE, ESP_AVRC_RN_VOLUME_CHANGE, 0);
- }
- }
- void bt_av_notify_evt_handler(uint8_t event_id, esp_avrc_rn_param_t *event_parameter)
- {
- switch (event_id) {
- /* when volume changed locally on target, this event comes */
- case ESP_AVRC_RN_VOLUME_CHANGE: {
- ESP_LOGI(BT_RC_CT_TAG, "Volume changed: %d", event_parameter->volume);
- ESP_LOGI(BT_RC_CT_TAG, "Set absolute volume: volume %d", event_parameter->volume + 5);
- esp_avrc_ct_send_set_absolute_volume_cmd(APP_RC_CT_TL_RN_VOLUME_CHANGE, event_parameter->volume + 5);
- bt_av_volume_changed();
- break;
- }
- /* other */
- default:
- break;
- }
- }
- /* AVRC controller event handler */
- static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param)
- {
- ESP_LOGD(BT_RC_CT_TAG, "%s evt %d", __func__, event);
- esp_avrc_ct_cb_param_t *rc = (esp_avrc_ct_cb_param_t *)(p_param);
- switch (event) {
- /* when connection state changed, this event comes */
- case ESP_AVRC_CT_CONNECTION_STATE_EVT: {
- uint8_t *bda = rc->conn_stat.remote_bda;
- ESP_LOGI(BT_RC_CT_TAG, "AVRC conn_state event: state %d, [%02x:%02x:%02x:%02x:%02x:%02x]",
- rc->conn_stat.connected, bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]);
- if (rc->conn_stat.connected) {
- esp_avrc_ct_send_get_rn_capabilities_cmd(APP_RC_CT_TL_GET_CAPS);
- } else {
- s_avrc_peer_rn_cap.bits = 0;
- }
- break;
- }
- /* when passthrough responsed, this event comes */
- case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT: {
- ESP_LOGI(BT_RC_CT_TAG, "AVRC passthrough response: key_code 0x%x, key_state %d", rc->psth_rsp.key_code, rc->psth_rsp.key_state);
- break;
- }
- /* when metadata responsed, this event comes */
- case ESP_AVRC_CT_METADATA_RSP_EVT: {
- ESP_LOGI(BT_RC_CT_TAG, "AVRC metadata response: attribute id 0x%x, %s", rc->meta_rsp.attr_id, rc->meta_rsp.attr_text);
- free(rc->meta_rsp.attr_text);
- break;
- }
- /* when notification changed, this event comes */
- case ESP_AVRC_CT_CHANGE_NOTIFY_EVT: {
- ESP_LOGI(BT_RC_CT_TAG, "AVRC event notification: %d", rc->change_ntf.event_id);
- bt_av_notify_evt_handler(rc->change_ntf.event_id, &rc->change_ntf.event_parameter);
- break;
- }
- /* when indicate feature of remote device, this event comes */
- case ESP_AVRC_CT_REMOTE_FEATURES_EVT: {
- ESP_LOGI(BT_RC_CT_TAG, "AVRC remote features %x, TG features %x", rc->rmt_feats.feat_mask, rc->rmt_feats.tg_feat_flag);
- /* DEBUG: - hack to try and make reconnection more resilient*/
- // ESP_LOGI(BT_AV_TAG, "a2dp connected");
- // s_a2d_state = APP_AV_STATE_CONNECTED;
- // s_media_state = APP_AV_MEDIA_STATE_IDLE;
- break;
- }
- /* when get supported notification events capability of peer device, this event comes */
- case ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT: {
- ESP_LOGI(BT_RC_CT_TAG, "remote rn_cap: count %d, bitmask 0x%x", rc->get_rn_caps_rsp.cap_count,
- rc->get_rn_caps_rsp.evt_set.bits);
- s_avrc_peer_rn_cap.bits = rc->get_rn_caps_rsp.evt_set.bits;
- bt_av_volume_changed();
- break;
- }
- /* when set absolute volume responsed, this event comes */
- case ESP_AVRC_CT_SET_ABSOLUTE_VOLUME_RSP_EVT: {
- ESP_LOGI(BT_RC_CT_TAG, "Set absolute volume response: volume %d", rc->set_volume_rsp.volume);
- break;
- }
- /* other */
- default: {
- ESP_LOGE(BT_RC_CT_TAG, "%s unhandled event: %d", __func__, event);
- break;
- }
- }
- }
- /* Initialise GPIOS*/
- static void app_gpio_init()
- {
- /* Set up button pins*/
- gpio_set_direction(BUTTON_PIN, GPIO_MODE_INPUT);
- gpio_set_pull_mode(BUTTON_PIN, GPIO_PULLDOWN_ONLY);
- gpio_set_intr_type(BUTTON_PIN, GPIO_INTR_POSEDGE);
- gpio_install_isr_service(ESP_INR_FLAG_DEFAULT);
- gpio_isr_handler_add(BUTTON_PIN, button_isr_handler, NULL);
- /* Set up interrupt task*/
- xTaskCreate(interrupt_task, "interrupt_task", 4096, NULL, 10, &ISR);
- /* Set up LED pin*/
- gpio_set_direction(LED_PIN, GPIO_MODE_OUTPUT);
- gpio_set_level(LED_PIN, 1);
- }
- void IRAM_ATTR button_isr_handler(void *arg){
- xTaskResumeFromISR(ISR);
- }
- static void interrupt_task(void *arg){
- /* Button and led logic variables*/
- bool led_status = false;
- bool button_state = false;
- /* Debounce delay variables for TODO: implement in future*/
- TickType_t xLastWakeTime;
- const TickType_t xFrequency = 1;
- BaseType_t xWasDelayed;
- /* Create the queue to pass data to the A2DP audio data loader*/
- buttonQueue = xQueueCreate(1, sizeof(led_status));
- if (buttonQueue == 0)
- {
- ESP_LOGI(APP_TAG, "Failed to create buttonQueue= %p\n", buttonQueue);
- }
- // bool old_button_state = false;
- while(1){
- vTaskSuspend(NULL);
- vTaskDelay(10/portTICK_PERIOD_MS); // delay for a modicum of debounce
- // xTaskDelayUntil(10/portTICK_PERIOD_MS); // delay for a modicum of debounce
- button_state = (bool)gpio_get_level(BUTTON_PIN);
- if(button_state /*&& button_state != old_button_state*/)
- {
- led_status = !led_status;
- // old_button_state = button_state;
- ESP_LOGI(APP_TAG,"Button pressed!");
- xQueueSend(buttonQueue, &button_state, (TickType_t)0);
- }
- gpio_set_level(LED_PIN, led_status);
- }
- }
- static void sample_mix_task(void *arg)
- {
- static bool button_pressed = false;
- static int8_t raw_data[SAMPLE_STREAM_BUFFER_SIZE/2]; // Needs to be signed int for mixing and volume to work
- static int8_t raw_dataA[SAMPLE_STREAM_BUFFER_SIZE/2]; // Needs to be signed int for mixing and volume to work
- static int8_t raw_dataB[SAMPLE_STREAM_BUFFER_SIZE/2]; // Needs to be signed int for mixing and volume to work
- int8_t * rawArrayPointer = raw_dataA; // point to the array of mixed samples
- uint8_t * sampleArrayPointer = sample_data; // point to the array of mixed samples
- uint8_t * blankArrayPointer = blank_data; // point to the array of zeroes
- static int32_t sent = 0;
- int count = 0;
- int waitCount = 0;
- uint8_t increment = 0; // where we look in the volume divisor array
- uint8_t volume_divisor = volumeDivisors[increment]; // start with full volume
- static uint16_t pointerOffset = 0; // where in sample array to start reading from
- static t_stAudioSample audio_clip;
- audio_clip.data_pointer = sampleArrayPointer;
- audio_clip.number_of_bytes = SAMPLE_STREAM_BUFFER_SIZE;
- static t_stAudioSample blank_clip;
- blank_clip.data_pointer = blankArrayPointer;
- blank_clip.number_of_bytes = SAMPLE_STREAM_BUFFER_SIZE;
- // make the sample buffer pointer queue
- sampleBufferQueue = xQueueCreate(2, sizeof(t_stAudioSample));
- if (sampleBufferQueue == NULL)
- {
- ESP_LOGI(APP_TAG, "Failed to create sampleBufferQueue= %p\n", sampleBufferQueue);
- }
- while(1)
- {
- if(xQueueReceive(buttonQueue, &button_pressed, (TickType_t)0))
- {
- fseek(sample_fileA, 0, SEEK_SET);
- fseek(sample_fileB, 0, SEEK_SET);
- fseek(sample_fileC, 0, SEEK_SET);
- waitCount = 0; // start counter from zero when button pressed
- }
- if(button_pressed && uxQueueSpacesAvailable(sampleBufferQueue))
- {
- int lRead = fread(raw_data, 1, SAMPLE_STREAM_BUFFER_SIZE/2, sample_fileC);
- // int lReadB = fread(raw_dataB, 1, SAMPLE_STREAM_BUFFER_SIZE/2, sample_fileB);
- if (lRead < SAMPLE_STREAM_BUFFER_SIZE/2) {
- button_pressed = false; // stop playing at the end of the sample
- // Pad the buffer with silence
- // int i = 0;
- // for (i = lRead; i < (SAMPLE_STREAM_BUFFER_SIZE >> 1); i+=2 ) {
- // sample_data[(i << 1) ] = 0;
- // sample_data[(i << 1) + 1] = 0;
- // }
- ESP_LOGI(APP_TAG, "End of sample, count %d, wait count %d", count, waitCount);
- count = 0;
- waitCount = 0;
- /* Change volume if desired*/
- // increment = (increment + 1 ) % NUM_OF_VOLUME_SETTINGS;
- // volume_divisor = volumeDivisors[increment];
- }
- /* Method for converting mono sample to stereo*/
- for (int i = 0; i < lRead; i += 2) {
- /* Right ear audio */
- //sample_data[(i << 1) ] = 0; //raw_data[i] / 1 ; // least significant element
- sample_data[(i << 1) + 1 + pointerOffset] = raw_data[i + 1] /* / volume_divisor */; // most significant element
- /* Left ear audio */
- //sample_data[((i+1) << 1) ] = 0; //raw_data[i] / 1 ; // least significant element
- sample_data[((i+1) << 1) + 1 + pointerOffset] = raw_data[i + 1] /* / volume_divisor */; // most significant element
- }
- audio_clip.sample_count = count++;
- // if (count > 90)
- // {
- // button_pressed = false; // DEBUG see error only happens if running past 100 samples
- // count = 0;
- // fseek(sample_fileB, 0, SEEK_SET);
- // }
- audio_clip.data_pointer = sampleArrayPointer + pointerOffset;
- sent = xQueueSend(sampleBufferQueue, &audio_clip, (TickType_t)0);
- //rawArrayPointer = rawArrayPointer == raw_dataA ? raw_dataB : raw_dataA; // toggle between two arrays
- pointerOffset = pointerOffset == 0 ? SAMPLE_STREAM_BUFFER_SIZE : 0; // toggle between beginning and halfway through the array.
- }
- else if (button_pressed)
- {
- waitCount++; // count how many times this happened
- //sent = xQueueSend(sampleBufferQueue, &blank_clip, (TickType_t)0);
- ulTaskNotifyTake(true, (TickType_t)10 ); // was 10, a bit crunchy with toms, got better with 100
- }
- else if (uxQueueSpacesAvailable(sampleBufferQueue))
- {
- sent = xQueueSend(sampleBufferQueue, &blank_clip, (TickType_t)10);
- }
- else
- {
- //sent = xQueueSend(sampleBufferQueue, &blank_clip, (TickType_t)0);
- ulTaskNotifyTake(true, (TickType_t)10 ); // was 10, a bit crunchy with toms, got better with 100
- // // Just send zeroes while no sample is playing
- }
- /* Wait for the BT audio callback to finish with samples*/
- // ulTaskNotifyTake(true, (TickType_t)portMAX_DELAY ); // was 10, a bit crunchy with toms, got better with 100
- // vTaskDelay(0); // virtual delay to prevent blocking and core wdt firing
- }
- }
- /********************************
- * Test SPIFFS
- ********************************/
- void app_test_spiffs()
- {
- init_spiffs();
- ESP_LOGE(TAG, "list files in spiffs");
- list_directory("");
- struct stat st;
- if (stat("/R8-OpenSnare-MonoS16PCM44k.raw", &st) == 0) {
- ESP_LOGI(TAG, "R8-OpenSnare-MonoS16PCM44k.raw found");
- sample_fileA = fopen("/R8-OpenSnare-MonoS16PCM44k.raw", "rb");
- sample_file = sample_fileA;
- } else {
- ESP_LOGI(TAG, "R8-OpenSnare-MonoS16PCM44k.raw not found");
- sample_file = NULL;
- }
- if (stat("/R8-RoomTom1-MonoS16PCM44k.raw", &st) == 0) {
- ESP_LOGI(TAG, "R8-RoomTom1-MonoS16PCM44k.raw found");
- sample_fileB = fopen("/R8-RoomTom1-MonoS16PCM44k.raw", "rb");
- sample_file = sample_fileB;
- } else {
- ESP_LOGI(TAG, "R8-RoomTom1-MonoS16PCM44k.raw not found");
- }
- if (stat("/R8-CrashCymbal-M-S16PCM44k.raw", &st) == 0) {
- ESP_LOGI(TAG, "R8-CrashCymbal-M-S16PCM44k.raw found");
- sample_fileC = fopen("/R8-CrashCymbal-M-S16PCM44k.raw", "rb");
- sample_file = sample_fileC;
- } else {
- ESP_LOGI(TAG, "R8-CrashCymbal-M-S16PCM44k.raw not found");
- sample_file = NULL;
- }
- }
- /*********************************
- * MAIN ENTRY POINT
- ********************************/
- void app_main(void)
- {
- /* initialize NVS — it is used to store PHY calibration data */
- esp_err_t ret = nvs_flash_init();
- if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
- ESP_ERROR_CHECK(nvs_flash_erase());
- ret = nvs_flash_init();
- }
- ESP_ERROR_CHECK(ret);
- /*
- * This example only uses the functions of Classical Bluetooth.
- * So release the controller memory for Bluetooth Low Energy.
- */
- ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_BLE));
- esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
- if (esp_bt_controller_init(&bt_cfg) != ESP_OK) {
- ESP_LOGE(BT_AV_TAG, "%s initialize controller failed\n", __func__);
- return;
- }
- if (esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT) != ESP_OK) {
- ESP_LOGE(BT_AV_TAG, "%s enable controller failed\n", __func__);
- return;
- }
- if (esp_bluedroid_init() != ESP_OK) {
- ESP_LOGE(BT_AV_TAG, "%s initialize bluedroid failed\n", __func__);
- return;
- }
- if (esp_bluedroid_enable() != ESP_OK) {
- ESP_LOGE(BT_AV_TAG, "%s enable bluedroid failed\n", __func__);
- return;
- }
- #if (CONFIG_BT_SSP_ENABLED == true)
- /* set default parameters for Secure Simple Pairing */
- esp_bt_sp_param_t param_type = ESP_BT_SP_IOCAP_MODE;
- esp_bt_io_cap_t iocap = ESP_BT_IO_CAP_IO;
- esp_bt_gap_set_security_param(param_type, &iocap, sizeof(uint8_t));
- #endif
- /*
- * Set default parameters for Legacy Pairing
- * Use variable pin, input pin code when pairing
- */
- esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_VARIABLE;
- esp_bt_pin_code_t pin_code;
- esp_bt_gap_set_pin(pin_type, 0, pin_code);
- bt_app_task_start_up();
- /* Bluetooth device name, connection mode and profile set up */
- bt_app_work_dispatch(bt_av_hdl_stack_evt, BT_APP_STACK_UP_EVT, NULL, 0, NULL);
- /* Set up GPIOs*/
- app_gpio_init();
- /* Test if can open file in SPIFFS*/
- app_test_spiffs();
- /* Set up sample preparation task*/
- xTaskCreatePinnedToCore(sample_mix_task, "sample_mix_task", 8192, NULL, 10, &SM_task, 1);// pinned to core 1 as BT is on core 0
- // xTaskCreate(sample_mix_task, "sample_mix_task", 8192, NULL, 10, &SM_task);// pinned to core 1 as BT is on core 0
- /* Start a non-returning while loop*/
- while(1)
- {
- vTaskDelay(10/portTICK_PERIOD_MS); // required to prevent watchdog from firing
- }
- }