diff --git a/docs/en/api-reference/bluetooth/esp_a2dp.rst b/docs/en/api-reference/bluetooth/esp_a2dp.rst index 8e1105e2583..eebf1e764be 100644 --- a/docs/en/api-reference/bluetooth/esp_a2dp.rst +++ b/docs/en/api-reference/bluetooth/esp_a2dp.rst @@ -13,6 +13,8 @@ Application Examples - :example:`bluetooth/bluedroid/classic_bt/a2dp_source` demonstrates how to use A2DP APIs to transmit audio streams. +- :example:`bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac` demonstrates how to use the AAC codec in an A2DP sink. + - :example:`bluetooth/bluedroid/coex/a2dp_gatts_coex` demonstrates how to use the ESP-IDF A2DP-GATTS_COEX demo to create a GATT service and A2DP profile. API Reference diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/CMakeLists.txt b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/CMakeLists.txt new file mode 100644 index 00000000000..6f3e5633b03 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/CMakeLists.txt @@ -0,0 +1,8 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.22) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. +idf_build_set_property(MINIMAL_BUILD ON) +project(a2dp_sink_stream_aac) diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/README.md b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/README.md new file mode 100644 index 00000000000..c673a4f4dda --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/README.md @@ -0,0 +1,79 @@ +| Supported Targets | ESP32 | ESP32-S31 | +| ----------------- | ----- | --------- | + +A2DP-SINK-STREAM-AAC EXAMPLE +====================== + +Example of A2DP audio sink role with **external codec** and **software decoding** (SBC and AAC). + +This example uses the Bluedroid **external codec** path: encoded media (SBC or MPEG-2/4 AAC) is delivered to the application through `esp_a2d_sink_register_audio_data_callback()`. The application decodes the stream with [esp_audio_codec](https://components.espressif.com/components/espressif/esp_audio_codec). + +## Required components + +- [bt_app_core_utils](../common/bt_app_core_utils) +- [bredr_app_common_utils](../common/bredr_app_common_utils) +- [a2dp_sink_common_utils](../common/a2dp_utils/a2dp_sink_common_utils) +- [a2dp_sink_int_codec_utils](../common/a2dp_utils/a2dp_sink_int_codec_utils) (I2S / DAC output) +- [a2dp_sink_ext_codec_utils](../common/a2dp_utils/a2dp_sink_ext_codec_utils) +- [espressif/esp_audio_codec](https://components.espressif.com/components/espressif/esp_audio_codec) + +Detailed information can be viewed through the [../common/README.md](../common/README.md). + +## How to use this example + +### Hardware Required + +To play the sound, there is a need of loudspeaker and possibly an external I2S codec. Otherwise the example will only show a count of audio data packets received silently. Internal DAC can be selected and in this case external I2S codec may not be needed. + +For the I2S codec, pick whatever chip or board works for you; this code was written using a PCM5102 chip, but other I2S boards and chips will probably work as well. The default I2S connections are shown below, but these can be changed in menuconfig: + +| ESP pin | I2S signal | +| :-------- | :----------- | +| GPIO27 | LRCK | +| GPIO25 | DATA | +| GPIO26 | BCK | + +If the internal DAC is selected, analog audio will be available on GPIO25 and GPIO26. The output resolution on these pins will always be limited to 8 bit because of the internal structure of the DACs. + +### Configure the project + +``` +idf.py menuconfig +``` + +* Choose external I2S codec or internal DAC for audio output, and configure the output PINs under A2DP Sink Internal Codec Example Configuration. + +### Build and Flash + +Build the project and flash it to the board, then run monitor tool to view serial output. + +``` +idf.py -p PORT flash monitor +``` + +(To exit the serial monitor, type ``Ctrl-]``.) + +## Example Output + +After the program is started, the example starts inquiry scan and page scan, awaiting being discovered and connected. Other bluetooth devices such as smart phones can discover a device named "ESP_SPEAKER_AAC". A smartphone or another ESP-IDF example of A2DP source can be used to connect to the local device. + +Once A2DP connection is set up, there will be a notification message with the remote device's bluetooth MAC address like the following: + +``` +I (106427) BT_AV: A2DP connection state: Connected, [64:a2:f9:69:57:a4] +``` + +If a smartphone is used to connect to local device, starting to play music with an APP will result in the transmission of audio stream. The transmitting of audio stream will be visible in the application log including a count of audio data packets, like this: + +``` +I (120627) BT_AV: A2DP audio state: Started +I (122697) BT_AV: Undecoded audio package count: 100 +I (124697) BT_AV: Undecoded audio package count: 200 +I (126697) BT_AV: Undecoded audio package count: 300 +I (128697) BT_AV: Undecoded audio package count: 400 +``` + + +## Troubleshooting + +For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/main/CMakeLists.txt b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/main/CMakeLists.txt new file mode 100644 index 00000000000..8042bdcecdc --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/main/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register(SRCS "main.c" + PRIV_REQUIRES bt bt_app_core_utils bredr_app_common_utils a2dp_sink_common_utils + PRIV_REQUIRES a2dp_sink_ext_codec_utils a2dp_sink_int_codec_utils + INCLUDE_DIRS ".") diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/main/Kconfig.projbuild b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/main/Kconfig.projbuild new file mode 100644 index 00000000000..0753260782d --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/main/Kconfig.projbuild @@ -0,0 +1,8 @@ +menu "A2DP Example Configuration" + config EXAMPLE_LOCAL_DEVICE_NAME + string "Local Device Name" + default "ESP_SPEAKER_AAC" + help + Use this option to set local device name. + +endmenu diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/main/idf_component.yml b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/main/idf_component.yml new file mode 100644 index 00000000000..c14ba5bff24 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/main/idf_component.yml @@ -0,0 +1,12 @@ +dependencies: + bt_app_core_utils: + path: ${IDF_PATH}/examples/bluetooth/bluedroid/classic_bt/common/bt_app_core_utils + bredr_app_common_utils: + path: ${IDF_PATH}/examples/bluetooth/bluedroid/classic_bt/common/bredr_app_common_utils + a2dp_sink_common_utils: + path: ${IDF_PATH}/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_common_utils + a2dp_sink_int_codec_utils: + path: ${IDF_PATH}/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils + a2dp_sink_ext_codec_utils: + path: ${IDF_PATH}/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_ext_codec_utils + espressif/esp_audio_codec: ^2.4.0 diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/main/main.c b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/main/main.c new file mode 100644 index 00000000000..487090177f6 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/main/main.c @@ -0,0 +1,537 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "freertos/semphr.h" +#include "esp_log.h" +#include "esp_bt_device.h" +#include "esp_gap_bt_api.h" +#include "esp_a2dp_api.h" +#include "esp_audio_dec.h" +#include "esp_aac_dec.h" +#include "esp_sbc_dec.h" +#include "esp_audio_dec_reg.h" +#include "bt_app_core_utils.h" +#include "bredr_app_common_utils.h" +#include "a2dp_sink_common_utils.h" +#include "a2dp_utils_tags.h" +#include "a2dp_sink_int_codec_utils.h" +#include "a2dp_sink_ext_codec_utils.h" +#include "audio_sink_service.h" + +#define BT_A2DP_RAW_Q_DEPTH 24 +#define BT_A2DP_DECODE_TASK_STACK 4096 +#ifndef BT_A2DP_DECODE_TASK_PRIO +#define BT_A2DP_DECODE_TASK_PRIO (tskIDLE_PRIORITY + 5) +#endif + +static QueueHandle_t s_a2dp_raw_q; +static TaskHandle_t s_a2dp_dec_task; +static SemaphoreHandle_t s_a2dp_raw_mux; +static bool s_a2dp_raw_started; +static uint32_t s_a2dp_raw_pkt_cnt; +static esp_audio_dec_handle_t s_dec_hdl; +static bool s_dec_opened; + +/* device name */ +static const char local_device_name[] = CONFIG_EXAMPLE_LOCAL_DEVICE_NAME; + +/* event for stack up */ +enum { + BT_APP_EVT_STACK_UP = 0, +}; + +/******************************** + * STATIC FUNCTION DECLARATIONS + *******************************/ + +/* Device callback function */ +static void bt_app_dev_cb(esp_bt_dev_cb_event_t event, esp_bt_dev_cb_param_t *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 sink */ +static void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param); + +/* callback function for A2DP sink undecoded audio data */ +static void bt_app_a2d_audio_data_cb(esp_a2d_conn_hdl_t conn_hdl, esp_a2d_audio_buff_t *audio_buf); + +/* handler for bluetooth stack enabled events */ +static void bt_av_hdl_stack_evt(uint16_t event, void *p_param); + +/******************************* + * STATIC FUNCTION DEFINITIONS + ******************************/ + +static int bt_app_mcc_get_sample_rate(const esp_a2d_mcc_t *mcc) +{ + if (mcc->type == ESP_A2D_MCT_SBC) { + const esp_a2d_cie_sbc_t *sbc = &mcc->cie.sbc_info; + if (sbc->samp_freq & ESP_A2D_SBC_CIE_SF_48K) { + return 48000; + } + if (sbc->samp_freq & ESP_A2D_SBC_CIE_SF_44K) { + return 44100; + } + if (sbc->samp_freq & ESP_A2D_SBC_CIE_SF_32K) { + return 32000; + } + if (sbc->samp_freq & ESP_A2D_SBC_CIE_SF_16K) { + return 16000; + } + return 44100; + } + if (mcc->type == ESP_A2D_MCT_M24) { + const esp_a2d_cie_m24_t *m24 = &mcc->cie.m24_info; + if (m24->samp_freq2 & ESP_A2D_M24_CIE_SF2_96K) { + return 96000; + } + if (m24->samp_freq2 & ESP_A2D_M24_CIE_SF2_88K) { + return 88200; + } + if (m24->samp_freq2 & ESP_A2D_M24_CIE_SF2_64K) { + return 64000; + } + if (m24->samp_freq2 & ESP_A2D_M24_CIE_SF2_48K) { + return 48000; + } + if (m24->samp_freq1 & ESP_A2D_M24_CIE_SF1_44K) { + return 44100; + } + if (m24->samp_freq1 & ESP_A2D_M24_CIE_SF1_32K) { + return 32000; + } + if (m24->samp_freq1 & ESP_A2D_M24_CIE_SF1_24K) { + return 24000; + } + if (m24->samp_freq1 & ESP_A2D_M24_CIE_SF1_22K) { + return 22050; + } + if (m24->samp_freq1 & ESP_A2D_M24_CIE_SF1_16K) { + return 16000; + } + if (m24->samp_freq1 & ESP_A2D_M24_CIE_SF1_12K) { + return 12000; + } + if (m24->samp_freq1 & ESP_A2D_M24_CIE_SF1_11K) { + return 11025; + } + if (m24->samp_freq1 & ESP_A2D_M24_CIE_SF1_8K) { + return 8000; + } + return 44100; + } + return 44100; +} + +static int bt_app_mcc_get_channel_count(const esp_a2d_mcc_t *mcc) +{ + if (mcc->type == ESP_A2D_MCT_SBC) { + if (mcc->cie.sbc_info.ch_mode & ESP_A2D_SBC_CIE_CH_MODE_MONO) { + return 1; + } + return 2; + } + if (mcc->type == ESP_A2D_MCT_M24) { + if (mcc->cie.m24_info.ch & ESP_A2D_M24_CIE_CH_1) { + return 1; + } + return 2; + } + return 2; +} + +static void bt_app_audio_decoder_close(void) +{ + if (!s_dec_opened) { + return; + } + esp_audio_dec_close(s_dec_hdl); + s_dec_hdl = NULL; + s_dec_opened = false; +} + +static esp_audio_err_t bt_app_audio_decoder_apply_mcc(const esp_a2d_mcc_t *mcc) +{ + esp_audio_err_t ret; + + if (mcc == NULL) { + return ESP_AUDIO_ERR_INVALID_PARAMETER; + } + if (mcc->type != ESP_A2D_MCT_SBC && mcc->type != ESP_A2D_MCT_M24) { + ESP_LOGW(BT_AV_TAG, "Unsupported A2DP codec type: %d", mcc->type); + return ESP_AUDIO_ERR_FAIL; + } + + bt_app_audio_decoder_close(); + + if (mcc->type == ESP_A2D_MCT_SBC) { + esp_sbc_dec_cfg_t sbc_cfg = ESP_SBC_DEC_CONFIG_DEFAULT(); + sbc_cfg.sbc_mode = ESP_SBC_MODE_STD; + sbc_cfg.ch_num = bt_app_mcc_get_channel_count(mcc); + sbc_cfg.enable_plc = true; + + esp_audio_dec_cfg_t dec_cfg = { + .type = ESP_AUDIO_TYPE_SBC, + .cfg = &sbc_cfg, + .cfg_sz = sizeof(esp_sbc_dec_cfg_t), + }; + + esp_sbc_dec_register(); + ret = esp_audio_dec_open(&dec_cfg, &s_dec_hdl); + if (ret != ESP_AUDIO_ERR_OK) { + ESP_LOGE(BT_AV_TAG, "esp_audio_dec_open (SBC) failed: %d", ret); + return ret; + } + ESP_LOGI(BT_AV_TAG, "SBC decoder open: %" PRIu32 " Hz, %d ch", + (uint32_t)bt_app_mcc_get_sample_rate(mcc), sbc_cfg.ch_num); + } else { + const esp_a2d_cie_m24_t *m24 = &mcc->cie.m24_info; + esp_aac_dec_cfg_t aac_cfg = ESP_AAC_DEC_CONFIG_DEFAULT(); + aac_cfg.sample_rate = bt_app_mcc_get_sample_rate(mcc); + aac_cfg.channel = bt_app_mcc_get_channel_count(mcc); + aac_cfg.bits_per_sample = ESP_AUDIO_BIT16; + aac_cfg.no_adts_header = true; + aac_cfg.aac_plus_enable = (m24->obj_type & (ESP_A2D_M24_CIE_OBJ_TYPE_4_HE_AAC | + ESP_A2D_M24_CIE_OBJ_TYPE_4_HE_AAC_V2)) != 0; + + esp_audio_dec_cfg_t dec_cfg = { + .type = ESP_AUDIO_TYPE_AAC, + .cfg = &aac_cfg, + .cfg_sz = sizeof(esp_aac_dec_cfg_t), + }; + + esp_aac_dec_register(); + ret = esp_audio_dec_open(&dec_cfg, &s_dec_hdl); + if (ret != ESP_AUDIO_ERR_OK) { + ESP_LOGE(BT_AV_TAG, "esp_audio_dec_open (AAC) failed: %d", ret); + return ret; + } + ESP_LOGI(BT_AV_TAG, "AAC decoder open: %" PRIu32 " Hz, %d ch, aac_plus=%d", + (uint32_t)aac_cfg.sample_rate, aac_cfg.channel, aac_cfg.aac_plus_enable); + } + + s_dec_opened = true; + return ESP_AUDIO_ERR_OK; +} + +static void bt_app_a2d_raw_audio_task(void *arg) +{ + esp_a2d_audio_buff_t *audio_buf; + uint32_t max_out_size = 4096; + uint8_t *out_buf = (uint8_t *)malloc((size_t)max_out_size); + if (out_buf == NULL) { + ESP_LOGE(BT_AV_TAG, "raw dec: out buffer alloc failed"); + vTaskDelete(NULL); + return; + } + + for (;;) { + xQueueReceive(s_a2dp_raw_q, &audio_buf, portMAX_DELAY); + + if (!s_dec_opened) { + esp_a2d_audio_buff_free(audio_buf); + continue; + } + + esp_audio_dec_in_raw_t in_raw = { + .buffer = audio_buf->data, + .len = audio_buf->data_len, + }; + esp_audio_dec_out_frame_t out_frame = { + .buffer = out_buf, + .len = max_out_size, + }; + + while (in_raw.len > 0) { + esp_audio_err_t dec_ret = esp_audio_dec_process(s_dec_hdl, &in_raw, &out_frame); + if (dec_ret != ESP_AUDIO_ERR_OK && dec_ret != ESP_AUDIO_ERR_BUFF_NOT_ENOUGH) { + ESP_LOGW(BT_AV_TAG, "esp_audio_dec_process failed: %d", dec_ret); + break; + } + + if (dec_ret == ESP_AUDIO_ERR_BUFF_NOT_ENOUGH) { + uint8_t *new_dec_buff = (uint8_t *)realloc(out_frame.buffer, (size_t)max_out_size * 2u); + if (new_dec_buff == NULL) { + break; + } + max_out_size *= 2; + out_buf = new_dec_buff; + out_frame.buffer = new_dec_buff; + out_frame.len = max_out_size; + continue; + } + + audio_sink_srv_data_output(out_frame.buffer, out_frame.decoded_size); + in_raw.buffer += in_raw.consumed; + in_raw.len -= in_raw.consumed; + } + + esp_a2d_audio_buff_free(audio_buf); + } +} + +void bt_a2d_sink_ext_codec_postproc_start(void) +{ + if (s_a2dp_raw_mux == NULL) { + s_a2dp_raw_mux = xSemaphoreCreateMutex(); + if (s_a2dp_raw_mux == NULL) { + return; + } + } + if (xSemaphoreTake(s_a2dp_raw_mux, portMAX_DELAY) != pdTRUE) { + return; + } + if (s_a2dp_raw_started) { + xSemaphoreGive(s_a2dp_raw_mux); + return; + } + + s_a2dp_raw_q = xQueueCreate(BT_A2DP_RAW_Q_DEPTH, sizeof(esp_a2d_audio_buff_t *)); + if (s_a2dp_raw_q == NULL) { + xSemaphoreGive(s_a2dp_raw_mux); + ESP_LOGE(BT_AV_TAG, "A2DP raw queue create failed"); + return; + } + if (xTaskCreate(bt_app_a2d_raw_audio_task, "a2dp_raw_dec", BT_A2DP_DECODE_TASK_STACK, + NULL, BT_A2DP_DECODE_TASK_PRIO, &s_a2dp_dec_task) != pdPASS) { + vQueueDelete(s_a2dp_raw_q); + s_a2dp_raw_q = NULL; + s_a2dp_dec_task = NULL; + xSemaphoreGive(s_a2dp_raw_mux); + ESP_LOGE(BT_AV_TAG, "A2DP raw decoder task create failed"); + return; + } + s_a2dp_raw_started = true; + xSemaphoreGive(s_a2dp_raw_mux); + ESP_LOGI(BT_AV_TAG, "A2DP raw postproc (queue %d) started", BT_A2DP_RAW_Q_DEPTH); +} + +void bt_a2d_sink_ext_codec_postproc_flush(void) +{ + esp_a2d_audio_buff_t *audio_buf; + + if (s_a2dp_raw_mux == NULL) { + return; + } + if (xSemaphoreTake(s_a2dp_raw_mux, portMAX_DELAY) != pdTRUE) { + return; + } + if (s_a2dp_dec_task != NULL) { + vTaskSuspend(s_a2dp_dec_task); + } + if (s_a2dp_raw_q != NULL) { + while (xQueueReceive(s_a2dp_raw_q, &audio_buf, 0) == pdTRUE) { + esp_a2d_audio_buff_free(audio_buf); + } + } + if (s_a2dp_dec_task != NULL) { + vTaskResume(s_a2dp_dec_task); + } + bt_app_audio_decoder_close(); + xSemaphoreGive(s_a2dp_raw_mux); +} + +static void bt_app_register_a2dp_sink_seps(void) +{ + esp_a2d_mcc_t mcc_aac = {0}; + mcc_aac.type = ESP_A2D_MCT_M24; + mcc_aac.cie.m24_info.drc = ESP_A2D_M24_CIE_DRC_NS; + mcc_aac.cie.m24_info.obj_type = ESP_A2D_M24_CIE_OBJ_TYPE_2_AAC_LC | + ESP_A2D_M24_CIE_OBJ_TYPE_4_AAC_LC | + ESP_A2D_M24_CIE_OBJ_TYPE_4_HE_AAC | + ESP_A2D_M24_CIE_OBJ_TYPE_4_HE_AAC_V2; + mcc_aac.cie.m24_info.samp_freq1 = ESP_A2D_M24_CIE_SF1_8K | + ESP_A2D_M24_CIE_SF1_11K | + ESP_A2D_M24_CIE_SF1_12K | + ESP_A2D_M24_CIE_SF1_16K | + ESP_A2D_M24_CIE_SF1_22K | + ESP_A2D_M24_CIE_SF1_24K | + ESP_A2D_M24_CIE_SF1_32K | + ESP_A2D_M24_CIE_SF1_44K; + mcc_aac.cie.m24_info.samp_freq2 = ESP_A2D_M24_CIE_SF2_48K | + ESP_A2D_M24_CIE_SF2_64K | + ESP_A2D_M24_CIE_SF2_88K | + ESP_A2D_M24_CIE_SF2_96K; + mcc_aac.cie.m24_info.ch = ESP_A2D_M24_CIE_CH_1 | + ESP_A2D_M24_CIE_CH_2; + mcc_aac.cie.m24_info.vbr = ESP_A2D_M24_CIE_VBR_SUPPORT; + mcc_aac.cie.m24_info.br1 = 0x7F & ESP_A2D_M24_CIE_BR1_MSK; + mcc_aac.cie.m24_info.br2 = 0xFF & ESP_A2D_M24_CIE_BR2_MSK; + mcc_aac.cie.m24_info.br3 = 0xFF & ESP_A2D_M24_CIE_BR3_MSK; + esp_a2d_sink_register_stream_endpoint(0, &mcc_aac); + + esp_a2d_mcc_t mcc_sbc = {0}; + mcc_sbc.type = ESP_A2D_MCT_SBC; + mcc_sbc.cie.sbc_info.samp_freq = ESP_A2D_SBC_CIE_SF_16K | + ESP_A2D_SBC_CIE_SF_32K | + ESP_A2D_SBC_CIE_SF_44K | + ESP_A2D_SBC_CIE_SF_48K; + mcc_sbc.cie.sbc_info.ch_mode = ESP_A2D_SBC_CIE_CH_MODE_MONO | + ESP_A2D_SBC_CIE_CH_MODE_DUAL_CHANNEL | + ESP_A2D_SBC_CIE_CH_MODE_STEREO | + ESP_A2D_SBC_CIE_CH_MODE_JOINT_STEREO; + mcc_sbc.cie.sbc_info.block_len = ESP_A2D_SBC_CIE_BLOCK_LEN_4 | + ESP_A2D_SBC_CIE_BLOCK_LEN_8 | + ESP_A2D_SBC_CIE_BLOCK_LEN_12 | + ESP_A2D_SBC_CIE_BLOCK_LEN_16; + mcc_sbc.cie.sbc_info.num_subbands = ESP_A2D_SBC_CIE_NUM_SUBBANDS_4 | ESP_A2D_SBC_CIE_NUM_SUBBANDS_8; + mcc_sbc.cie.sbc_info.alloc_mthd = ESP_A2D_SBC_CIE_ALLOC_MTHD_SNR | ESP_A2D_SBC_CIE_ALLOC_MTHD_LOUDNESS; + mcc_sbc.cie.sbc_info.min_bitpool = 2; + mcc_sbc.cie.sbc_info.max_bitpool = 250; + esp_a2d_sink_register_stream_endpoint(1, &mcc_sbc); +} + +static void bt_app_dev_cb(esp_bt_dev_cb_event_t event, esp_bt_dev_cb_param_t *param) +{ + bredr_app_dev_evt_def_hdl(event, param); +} + +static void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param) +{ + bredr_app_gap_evt_def_hdl(event, param); +} + +static void bt_app_a2d_hdl(uint16_t event, void *param) +{ + esp_a2d_cb_param_t *a2d = (esp_a2d_cb_param_t *)param; + + switch (event) { + case ESP_A2D_PROF_STATE_EVT: + case ESP_A2D_SNK_PSC_CFG_EVT: + case ESP_A2D_SNK_SET_DELAY_VALUE_EVT: + case ESP_A2D_SNK_GET_DELAY_VALUE_EVT: { + bt_a2d_evt_def_hdl(event, param); + break; + } + case ESP_A2D_CONNECTION_STATE_EVT: { + if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) { + bt_a2d_sink_ext_codec_postproc_flush(); + } + bt_a2d_evt_int_codec_hdl(event, param); + break; + } + case ESP_A2D_AUDIO_STATE_EVT: { + if (a2d->audio_stat.state == ESP_A2D_AUDIO_STATE_STARTED) { + bt_a2d_sink_ext_codec_postproc_start(); + } else if (a2d->audio_stat.state == ESP_A2D_AUDIO_STATE_SUSPEND) { + bt_a2d_sink_ext_codec_postproc_flush(); + } + bt_a2d_evt_int_codec_hdl(event, param); + break; + } + case ESP_A2D_AUDIO_CFG_EVT: + bt_app_audio_decoder_apply_mcc(&a2d->audio_cfg.mcc); + bt_a2d_evt_int_codec_hdl(event, param); + break; + case ESP_A2D_SEP_REG_STATE_EVT: { + bt_a2d_evt_ext_codec_hdl(event, param); + break; + } + default: + ESP_LOGE(BT_AV_TAG, "Invalid A2DP event: %d", event); + break; + } +} + +static void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param) +{ + switch (event) { + case ESP_A2D_PROF_STATE_EVT: + case ESP_A2D_SNK_PSC_CFG_EVT: + case ESP_A2D_SNK_SET_DELAY_VALUE_EVT: + case ESP_A2D_SNK_GET_DELAY_VALUE_EVT: + case ESP_A2D_CONNECTION_STATE_EVT: + case ESP_A2D_AUDIO_STATE_EVT: + case ESP_A2D_AUDIO_CFG_EVT: + case ESP_A2D_SEP_REG_STATE_EVT: { + bt_app_work_dispatch(bt_app_a2d_hdl, event, param, sizeof(esp_a2d_cb_param_t), NULL, NULL); + break; + } + default: + ESP_LOGE(BT_AV_TAG, "Invalid A2DP event: %d", event); + break; + } +} + +static void bt_app_a2d_audio_data_cb(esp_a2d_conn_hdl_t conn_hdl, esp_a2d_audio_buff_t *audio_buf) +{ + (void)conn_hdl; + + if (audio_buf == NULL || audio_buf->data == NULL || audio_buf->data_len == 0) { + if (audio_buf != NULL) { + esp_a2d_audio_buff_free(audio_buf); + } + return; + } + + if (!s_a2dp_raw_started) { + esp_a2d_audio_buff_free(audio_buf); + return; + } + + if (++s_a2dp_raw_pkt_cnt % 100 == 0) { + ESP_LOGI(BT_AV_TAG, "Undecoded audio package count: %" PRIu32, s_a2dp_raw_pkt_cnt); + } + + if (xQueueSend(s_a2dp_raw_q, &audio_buf, 0) != pdTRUE) { + ESP_LOGW(BT_AV_TAG, "raw queue full, drop packet"); + esp_a2d_audio_buff_free(audio_buf); + } +} + +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 do the stack up, this event comes */ + case BT_APP_EVT_STACK_UP: { + esp_bt_gap_set_device_name(local_device_name); + esp_bt_dev_register_callback(bt_app_dev_cb); + esp_bt_gap_register_callback(bt_app_gap_cb); + + esp_a2d_register_callback(&bt_app_a2d_cb); + assert(esp_a2d_sink_init() == ESP_OK); + + bt_app_register_a2dp_sink_seps(); + esp_a2d_sink_register_audio_data_callback(bt_app_a2d_audio_data_cb); + + /* Get the default value of the delay value */ + esp_a2d_sink_get_delay_value(); + /* Get local device name */ + esp_bt_gap_get_device_name(); + + /* set discoverable and connectable mode, wait to be connected */ + esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); + break; + } + /* others */ + default: + ESP_LOGE(BT_AV_TAG, "%s unhandled event: %d", __func__, event); + break; + } +} + +/******************************* + * MAIN ENTRY POINT + ******************************/ + +void app_main(void) +{ + ESP_ERROR_CHECK(bredr_app_common_init()); + + 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_EVT_STACK_UP, NULL, 0, NULL, NULL); +} diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/sdkconfig.ci.ca_dis b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/sdkconfig.ci.ca_dis new file mode 100644 index 00000000000..9d5c2a060d0 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/sdkconfig.ci.ca_dis @@ -0,0 +1,9 @@ +# Override some defaults so BT stack is enabled and +# Classic BT is enabled and BT_DRAM_RELEASE is disabled +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=y +CONFIG_BT_BLUEDROID_ENABLED=y +CONFIG_BT_CLASSIC_ENABLED=y +CONFIG_BT_A2DP_ENABLE=y +CONFIG_BT_AVRCP_CT_COVER_ART_ENABLED=n +CONFIG_DAC_DMA_AUTO_16BIT_ALIGN=n diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/sdkconfig.ci.test b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/sdkconfig.ci.test new file mode 100644 index 00000000000..18281d6e860 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/sdkconfig.ci.test @@ -0,0 +1 @@ +CONFIG_EXAMPLE_LOCAL_DEVICE_NAME="${CI_PIPELINE_ID}_A2DP_SINK_AAC" diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/sdkconfig.defaults b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/sdkconfig.defaults new file mode 100644 index 00000000000..7a28de674f7 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/sdkconfig.defaults @@ -0,0 +1,12 @@ +# Override some defaults so BT stack is enabled and +# Classic BT is enabled and BT_DRAM_RELEASE is disabled +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=y +CONFIG_BT_BLUEDROID_ENABLED=y +CONFIG_BT_CLASSIC_ENABLED=y +CONFIG_BT_A2DP_ENABLE=y +CONFIG_BT_BLE_ENABLED=n +CONFIG_DAC_DMA_AUTO_16BIT_ALIGN=n +CONFIG_BT_A2DP_USE_EXTERNAL_CODEC=y +CONFIG_BT_A2DP_SEP_NUM_MAX=2 +CONFIG_BT_A2DP_CODEC_AAC_ENABLED=y diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/sdkconfig.defaults.esp32s31 b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/sdkconfig.defaults.esp32s31 new file mode 100644 index 00000000000..9a77739a3d4 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/sdkconfig.defaults.esp32s31 @@ -0,0 +1,2 @@ +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y +CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_IDLE=y diff --git a/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_ext_codec_utils/a2dp_sink_ext_codec_utils.c b/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_ext_codec_utils/a2dp_sink_ext_codec_utils.c index e19ca2d95d8..7b13596a5b1 100644 --- a/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_ext_codec_utils/a2dp_sink_ext_codec_utils.c +++ b/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_ext_codec_utils/a2dp_sink_ext_codec_utils.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -63,7 +63,7 @@ void bt_a2d_evt_ext_codec_hdl(uint16_t event, void *param) case ESP_A2D_AUDIO_CFG_EVT: { esp_a2d_mcc_t *p_mcc = &a2d->audio_cfg.mcc; ESP_LOGI(BT_AV_TAG, "A2DP audio stream configuration, codec type: %d", p_mcc->type); - /* for now only SBC stream is supported */ + if (p_mcc->type == ESP_A2D_MCT_SBC) { int sample_rate = 16000; if (p_mcc->cie.sbc_info.samp_freq & ESP_A2D_SBC_CIE_SF_32K) { @@ -82,6 +82,44 @@ void bt_a2d_evt_ext_codec_hdl(uint16_t event, void *param) p_mcc->cie.sbc_info.min_bitpool, p_mcc->cie.sbc_info.max_bitpool); ESP_LOGI(BT_AV_TAG, "Audio player configured, sample rate: %d", sample_rate); + } else if (p_mcc->type == ESP_A2D_MCT_M24) { + int sample_rate = 16000; + if (p_mcc->cie.m24_info.samp_freq2 & ESP_A2D_M24_CIE_SF2_96K) { + sample_rate = 96000; + } else if (p_mcc->cie.m24_info.samp_freq2 & ESP_A2D_M24_CIE_SF2_88K) { + sample_rate = 88200; + } else if (p_mcc->cie.m24_info.samp_freq2 & ESP_A2D_M24_CIE_SF2_64K) { + sample_rate = 64000; + } else if (p_mcc->cie.m24_info.samp_freq2 & ESP_A2D_M24_CIE_SF2_48K) { + sample_rate = 48000; + } else if (p_mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_44K) { + sample_rate = 44100; + } else if (p_mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_32K) { + sample_rate = 32000; + } else if (p_mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_24K) { + sample_rate = 24000; + } else if (p_mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_22K) { + sample_rate = 22050; + } else if (p_mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_16K) { + sample_rate = 16000; + } else if (p_mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_12K) { + sample_rate = 12000; + } else if (p_mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_11K) { + sample_rate = 11025; + } else if (p_mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_8K) { + sample_rate = 8000; + } + ESP_LOGI(BT_AV_TAG, "Configure audio player: 0x%x-0x%x-0x%x-0x%x-0x%x-0x%x-0x%x-0x%x-0x%x", + p_mcc->cie.m24_info.drc, + p_mcc->cie.m24_info.obj_type, + p_mcc->cie.m24_info.samp_freq1, + p_mcc->cie.m24_info.samp_freq2, + p_mcc->cie.m24_info.ch, + p_mcc->cie.m24_info.vbr, + p_mcc->cie.m24_info.br1, + p_mcc->cie.m24_info.br2, + p_mcc->cie.m24_info.br3); + ESP_LOGI(BT_AV_TAG, "Audio player configured, sample rate: %d", sample_rate); } break; } diff --git a/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/audio_sink_service_dac.c b/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/audio_sink_service_dac.c index 44132db0df8..87406ac73cf 100644 --- a/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/audio_sink_service_dac.c +++ b/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/audio_sink_service_dac.c @@ -179,7 +179,7 @@ void audio_sink_srv_codec_info_update(esp_a2d_mcc_t *mcc) { ESP_LOGI(AUDIO_SNK_SRV_DAC_TAG, "A2DP audio stream configuration, codec type: %d", mcc->type); audio_sink_srv_stop(); - /* for now only SBC stream is supported */ + if (mcc->type == ESP_A2D_MCT_SBC) { int sample_rate = 16000; int ch_count = 2; @@ -218,6 +218,64 @@ void audio_sink_srv_codec_info_update(esp_a2d_mcc_t *mcc) mcc->cie.sbc_info.min_bitpool, mcc->cie.sbc_info.max_bitpool); ESP_LOGI(AUDIO_SNK_SRV_DAC_TAG, "Audio player configured, sample rate: %d", sample_rate); + } else if (mcc->type == ESP_A2D_MCT_M24) { + int sample_rate = 16000; + int ch_count = 2; + if (mcc->cie.m24_info.samp_freq2 & ESP_A2D_M24_CIE_SF2_96K) { + sample_rate = 96000; + } else if (mcc->cie.m24_info.samp_freq2 & ESP_A2D_M24_CIE_SF2_88K) { + sample_rate = 88200; + } else if (mcc->cie.m24_info.samp_freq2 & ESP_A2D_M24_CIE_SF2_64K) { + sample_rate = 64000; + } else if (mcc->cie.m24_info.samp_freq2 & ESP_A2D_M24_CIE_SF2_48K) { + sample_rate = 48000; + } else if (mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_44K) { + sample_rate = 44100; + } else if (mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_32K) { + sample_rate = 32000; + } else if (mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_24K) { + sample_rate = 24000; + } else if (mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_22K) { + sample_rate = 22050; + } else if (mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_16K) { + sample_rate = 16000; + } else if (mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_12K) { + sample_rate = 12000; + } else if (mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_11K) { + sample_rate = 11025; + } else if (mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_8K) { + sample_rate = 8000; + } + + if (mcc->cie.m24_info.ch & ESP_A2D_M24_CIE_CH_1) { + ch_count = 1; + } + if (s_dac_cb.tx_chan) { + dac_continuous_del_channels(s_dac_cb.tx_chan); + s_dac_cb.tx_chan = NULL; + } + dac_continuous_config_t cont_cfg = { + .chan_mask = DAC_CHANNEL_MASK_ALL, + .desc_num = 8, + .buf_size = 2048, + .freq_hz = sample_rate, + .offset = 127, + .clk_src = DAC_DIGI_CLK_SRC_DEFAULT, // Using APLL as clock source to get a wider frequency range + .chan_mode = (ch_count == 1) ? DAC_CHANNEL_MODE_SIMUL : DAC_CHANNEL_MODE_ALTER, + }; + /* Allocate continuous channels */ + ESP_ERROR_CHECK(dac_continuous_new_channels(&cont_cfg, &s_dac_cb.tx_chan)); + ESP_LOGI(AUDIO_SNK_SRV_DAC_TAG, "Configure audio player: 0x%x-0x%x-0x%x-0x%x-0x%x-0x%x-0x%x-0x%x-0x%x", + mcc->cie.m24_info.drc, + mcc->cie.m24_info.obj_type, + mcc->cie.m24_info.samp_freq1, + mcc->cie.m24_info.samp_freq2, + mcc->cie.m24_info.ch, + mcc->cie.m24_info.vbr, + mcc->cie.m24_info.br1, + mcc->cie.m24_info.br2, + mcc->cie.m24_info.br3); + ESP_LOGI(AUDIO_SNK_SRV_DAC_TAG, "Audio player configured, sample rate: %d", sample_rate); } } diff --git a/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/audio_sink_service_i2s.c b/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/audio_sink_service_i2s.c index 9049d74710f..182a7a73ed7 100644 --- a/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/audio_sink_service_i2s.c +++ b/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/audio_sink_service_i2s.c @@ -189,7 +189,7 @@ void audio_sink_srv_codec_info_update(esp_a2d_mcc_t *mcc) { ESP_LOGI(AUDIO_SNK_SRV_I2S_TAG, "A2DP audio stream configuration, codec type: %d", mcc->type); audio_sink_srv_stop(); - /* for now only SBC stream is supported */ + if (mcc->type == ESP_A2D_MCT_SBC) { int sample_rate = 16000; int ch_count = 2; @@ -217,6 +217,53 @@ void audio_sink_srv_codec_info_update(esp_a2d_mcc_t *mcc) mcc->cie.sbc_info.min_bitpool, mcc->cie.sbc_info.max_bitpool); ESP_LOGI(AUDIO_SNK_SRV_I2S_TAG, "Audio player configured, sample rate: %d", sample_rate); + } else if (mcc->type == ESP_A2D_MCT_M24) { + int sample_rate = 16000; + int ch_count = 2; + if (mcc->cie.m24_info.samp_freq2 & ESP_A2D_M24_CIE_SF2_96K) { + sample_rate = 96000; + } else if (mcc->cie.m24_info.samp_freq2 & ESP_A2D_M24_CIE_SF2_88K) { + sample_rate = 88200; + } else if (mcc->cie.m24_info.samp_freq2 & ESP_A2D_M24_CIE_SF2_64K) { + sample_rate = 64000; + } else if (mcc->cie.m24_info.samp_freq2 & ESP_A2D_M24_CIE_SF2_48K) { + sample_rate = 48000; + } else if (mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_44K) { + sample_rate = 44100; + } else if (mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_32K) { + sample_rate = 32000; + } else if (mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_24K) { + sample_rate = 24000; + } else if (mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_22K) { + sample_rate = 22050; + } else if (mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_16K) { + sample_rate = 16000; + } else if (mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_12K) { + sample_rate = 12000; + } else if (mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_11K) { + sample_rate = 11025; + } else if (mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_8K) { + sample_rate = 8000; + } + + if (mcc->cie.m24_info.ch & ESP_A2D_M24_CIE_CH_1) { + ch_count = 1; + } + i2s_std_clk_config_t clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(sample_rate); + i2s_std_slot_config_t slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, ch_count); + ESP_ERROR_CHECK(i2s_channel_reconfig_std_clock(s_i2s_cb.tx_chan, &clk_cfg)); + ESP_ERROR_CHECK(i2s_channel_reconfig_std_slot(s_i2s_cb.tx_chan, &slot_cfg)); + ESP_LOGI(AUDIO_SNK_SRV_I2S_TAG, "Configure audio player: 0x%x-0x%x-0x%x-0x%x-0x%x-0x%x-0x%x-0x%x-0x%x", + mcc->cie.m24_info.drc, + mcc->cie.m24_info.obj_type, + mcc->cie.m24_info.samp_freq1, + mcc->cie.m24_info.samp_freq2, + mcc->cie.m24_info.ch, + mcc->cie.m24_info.vbr, + mcc->cie.m24_info.br1, + mcc->cie.m24_info.br2, + mcc->cie.m24_info.br3); + ESP_LOGI(AUDIO_SNK_SRV_I2S_TAG, "Audio player configured, sample rate: %d", sample_rate); } }