Skip to content

Commit

Permalink
optimized a2dp_sink audio datapath
Browse files Browse the repository at this point in the history
1. removed audio cache in BTC layer of Bluedroid
2. added flow control for audio data in application layer

Closes #9622
  • Loading branch information
BetterJincheng committed Nov 24, 2022
1 parent 3d1c15c commit 75b2e63
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ enum {

/* 18 frames is equivalent to 6.89*18*2.9 ~= 360 ms @ 44.1 khz, 20 ms mediatick */
#define MAX_OUTPUT_A2DP_SNK_FRAME_QUEUE_SZ (25)
#define JITTER_BUFFER_WATER_LEVEL (5)

#define BTC_A2DP_SNK_DATA_QUEUE_IDX (1)

Expand Down Expand Up @@ -688,9 +687,7 @@ UINT8 btc_a2dp_sink_enque_buf(BT_HDR *p_pkt)
p_msg->num_frames_to_be_processed = (*((UINT8 *)(p_msg + 1) + p_msg->offset)) & 0x0f;
APPL_TRACE_VERBOSE("btc_a2dp_sink_enque_buf %d + \n", p_msg->num_frames_to_be_processed);
fixed_queue_enqueue(a2dp_sink_local_param.btc_aa_snk_cb.RxSbcQ, p_msg, FIXED_QUEUE_MAX_TIMEOUT);
if (fixed_queue_length(a2dp_sink_local_param.btc_aa_snk_cb.RxSbcQ) >= JITTER_BUFFER_WATER_LEVEL) {
osi_thread_post_event(a2dp_sink_local_param.btc_aa_snk_cb.data_ready_event, OSI_THREAD_MAX_TIMEOUT);
}
osi_thread_post_event(a2dp_sink_local_param.btc_aa_snk_cb.data_ready_event, OSI_THREAD_MAX_TIMEOUT);
} else {
/* let caller deal with a failed allocation */
APPL_TRACE_WARNING("btc_a2dp_sink_enque_buf No Buffer left - ");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,8 +275,8 @@ static void bt_av_hdl_a2d_evt(uint16_t event, void *p_param)
s_a2d_conn_state_str[a2d->conn_stat.state], bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]);
if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) {
esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);
bt_i2s_task_shut_down();
bt_i2s_driver_uninstall();
bt_i2s_task_shut_down();
} else if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_CONNECTED){
esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE);
bt_i2s_task_start_up();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "freertos/FreeRTOSConfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "bt_app_core.h"
Expand All @@ -22,6 +23,16 @@
#endif
#include "freertos/ringbuf.h"


#define RINGBUF_HIGHEST_WATER_LEVEL (32 * 1024)
#define RINGBUF_PREFETCH_WATER_LEVEL (20 * 1024)

enum {
RINGBUFFER_MODE_PROCESSING, /* ringbuffer is buffering incoming audio data, I2S is working */
RINGBUFFER_MODE_PREFETCHING, /* ringbuffer is buffering incoming audio data, I2S is waiting */
RINGBUFFER_MODE_DROPPING /* ringbuffer is not buffering (dropping) incoming audio data, I2S is working */
};

/*******************************
* STATIC FUNCTION DECLARATIONS
******************************/
Expand All @@ -43,6 +54,12 @@ static QueueHandle_t s_bt_app_task_queue = NULL; /* handle of work queue */
static TaskHandle_t s_bt_app_task_handle = NULL; /* handle of application task */
static TaskHandle_t s_bt_i2s_task_handle = NULL; /* handle of I2S task */
static RingbufHandle_t s_ringbuf_i2s = NULL; /* handle of ringbuffer for I2S */
static SemaphoreHandle_t s_i2s_write_semaphore = NULL;
static uint16_t ringbuffer_mode = RINGBUFFER_MODE_PROCESSING;

/*********************************
* EXTERNAL FUNCTION DECLARATIONS
********************************/
#ifndef CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC
extern i2s_chan_handle_t tx_chan;
#endif
Expand Down Expand Up @@ -101,18 +118,33 @@ static void bt_i2s_task_handler(void *arg)
{
uint8_t *data = NULL;
size_t item_size = 0;
/**
* The total length of DMA buffer of I2S is:
* `dma_frame_num * dma_desc_num * i2s_channel_num * i2s_data_bit_width / 8`.
* Transmit `dma_frame_num * dma_desc_num` bytes to DMA is trade-off.
*/
const size_t item_size_upto = 240 * 6;
size_t bytes_written = 0;

for (;;) {
/* receive data from ringbuffer and write it to I2S DMA transmit buffer */
data = (uint8_t *)xRingbufferReceive(s_ringbuf_i2s, &item_size, (TickType_t)portMAX_DELAY);
if (item_size != 0){
#ifdef CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC
i2s_write(0, data, item_size, &bytes_written, portMAX_DELAY);
#else
i2s_channel_write(tx_chan, data, item_size, &bytes_written, portMAX_DELAY);
#endif
vRingbufferReturnItem(s_ringbuf_i2s, (void *)data);
if (pdTRUE == xSemaphoreTake(s_i2s_write_semaphore, portMAX_DELAY)) {
for (;;) {
item_size = 0;
/* receive data from ringbuffer and write it to I2S DMA transmit buffer */
data = (uint8_t *)xRingbufferReceiveUpTo(s_ringbuf_i2s, &item_size, (TickType_t)pdMS_TO_TICKS(20), item_size_upto);
if (item_size == 0) {
ESP_LOGI(BT_APP_CORE_TAG, "ringbuffer underflowed! mode changed: RINGBUFFER_MODE_PREFETCHING");
ringbuffer_mode = RINGBUFFER_MODE_PREFETCHING;
break;
}

#ifdef CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC
i2s_write(0, data, item_size, &bytes_written, portMAX_DELAY);
#else
i2s_channel_write(tx_chan, data, item_size, &bytes_written, portMAX_DELAY);
#endif
vRingbufferReturnItem(s_ringbuf_i2s, (void *)data);
}
}
}
}
Expand Down Expand Up @@ -168,7 +200,14 @@ void bt_app_task_shut_down(void)

void bt_i2s_task_start_up(void)
{
if ((s_ringbuf_i2s = xRingbufferCreate(8 * 1024, RINGBUF_TYPE_BYTEBUF)) == NULL) {
ESP_LOGI(BT_APP_CORE_TAG, "ringbuffer data empty! mode changed: RINGBUFFER_MODE_PREFETCHING");
ringbuffer_mode = RINGBUFFER_MODE_PREFETCHING;
if ((s_i2s_write_semaphore = xSemaphoreCreateBinary()) == NULL) {
ESP_LOGE(BT_APP_CORE_TAG, "%s, Semaphore create failed", __func__);
return;
}
if ((s_ringbuf_i2s = xRingbufferCreate(RINGBUF_HIGHEST_WATER_LEVEL, RINGBUF_TYPE_BYTEBUF)) == NULL) {
ESP_LOGE(BT_APP_CORE_TAG, "%s, ringbuffer create failed", __func__);
return;
}
xTaskCreate(bt_i2s_task_handler, "BtI2STask", 1024, NULL, configMAX_PRIORITIES - 3, &s_bt_i2s_task_handle);
Expand All @@ -184,11 +223,44 @@ void bt_i2s_task_shut_down(void)
vRingbufferDelete(s_ringbuf_i2s);
s_ringbuf_i2s = NULL;
}
if (s_i2s_write_semaphore) {
vSemaphoreDelete(s_i2s_write_semaphore);
s_i2s_write_semaphore = NULL;
}
}

size_t write_ringbuf(const uint8_t *data, size_t size)
{
BaseType_t done = xRingbufferSend(s_ringbuf_i2s, (void *)data, size, (TickType_t)portMAX_DELAY);
size_t item_size = 0;
BaseType_t done = pdFALSE;

if (ringbuffer_mode == RINGBUFFER_MODE_DROPPING) {
ESP_LOGW(BT_APP_CORE_TAG, "ringbuffer is full, drop this packet!");
vRingbufferGetInfo(s_ringbuf_i2s, NULL, NULL, NULL, NULL, &item_size);
if (item_size <= RINGBUF_PREFETCH_WATER_LEVEL) {
ESP_LOGI(BT_APP_CORE_TAG, "ringbuffer data decreased! mode changed: RINGBUFFER_MODE_PROCESSING");
ringbuffer_mode = RINGBUFFER_MODE_PROCESSING;
}
return 0;
}

done = xRingbufferSend(s_ringbuf_i2s, (void *)data, size, (TickType_t)0);

if (!done) {
ESP_LOGW(BT_APP_CORE_TAG, "ringbuffer overflowed, ready to decrease data! mode changed: RINGBUFFER_MODE_DROPPING");
ringbuffer_mode = RINGBUFFER_MODE_DROPPING;
}

if (ringbuffer_mode == RINGBUFFER_MODE_PREFETCHING) {
vRingbufferGetInfo(s_ringbuf_i2s, NULL, NULL, NULL, NULL, &item_size);
if (item_size >= RINGBUF_PREFETCH_WATER_LEVEL) {
ESP_LOGI(BT_APP_CORE_TAG, "ringbuffer data increased! mode changed: RINGBUFFER_MODE_PROCESSING");
ringbuffer_mode = RINGBUFFER_MODE_PROCESSING;
if (pdFALSE == xSemaphoreGive(s_i2s_write_semaphore)) {
ESP_LOGE(BT_APP_CORE_TAG, "semphore give failed");
}
}
}

return done ? size : 0;
}

0 comments on commit 75b2e63

Please sign in to comment.