├── LICENSE ├── README.md ├── README_CN.md ├── examples ├── SDmusic │ └── SDmusic.ino ├── bluetoothAmplifier │ └── bluetoothAmplifier.ino ├── configAmplifier │ └── configAmplifier.ino └── customBluetooth │ └── customBluetooth.ino ├── keywords.txt ├── library.properties ├── resources └── images │ └── MAX98357A.png └── src ├── Biquad.cpp ├── Biquad.h ├── DFRobot_MAX98357A.cpp └── DFRobot_MAX98357A.h /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2010 DFRobot Co.Ltd 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DFRobot_MAX98357A 2 | * [中文版](./README_CN.md) 3 | 4 | This is a small and easy-to-use MAX98357A I2S amplifier. In combination with ESP32 controller, it can be turned into a Bluetooth speaker, WiFi walkie-talkie and device for voice recognition or network broadcast, etc. Insert an SD card into the module, then you can directly use it to play music. 5 | 6 | 7 | ![产品实物图](./resources/images/MAX98357A.png) 8 | 9 | 10 | ## Product Link (https://www.dfrobot.com/product-2614.html) 11 | SKU: DFR0954 12 | 13 | 14 | ## Table of Contents 15 | 16 | * [Summary](#summary) 17 | * [Installation](#installation) 18 | * [Methods](#methods) 19 | * [Compatibility](#compatibility) 20 | * [History](#history) 21 | * [Credits](#credits) 22 | 23 | 24 | ## Summary 25 | 26 | * Support direct insertion and surface mount, small and easy-to-use. 27 | * With onboard I2S amplifier, 3W power, and good sound quality. 28 | 29 | 30 | ## Installation 31 | 32 | To use this library, first download the library file, paste it into the \Arduino\libraries directory, then open the examples folder and run the demo in the folder. 33 | 34 | 35 | ## Methods 36 | 37 | ```C++ 38 | 39 | /** 40 | * @fn begin 41 | * @brief Init function 42 | * @param btName - Created Bluetooth device name 43 | * @param bclk - I2S communication pin number, serial clock (SCK), aka bit clock (BCK) 44 | * @param lrclk - I2S communication pin number, word select (WS), i.e. command (channel) select, used to switch between left and right channel data 45 | * @param din - I2S communication pin number, serial data signal (SD), used to transmit audio data in two's complement format 46 | * @return true on success, false on error 47 | */ 48 | bool begin(const char *btName="bluetoothAmplifier", 49 | int bclk=GPIO_NUM_25, 50 | int lrclk=GPIO_NUM_26, 51 | int din=GPIO_NUM_27); 52 | 53 | /** 54 | * @fn initI2S 55 | * @brief Initialize I2S 56 | * @param _bclk - I2S communication pin number, serial clock (SCK), aka bit clock (BCK) 57 | * @param _lrclk - I2S communication pin number, word select (WS), i.e. command (channel) select, used to switch between left and right channel data 58 | * @param _din - I2S communication pin number, serial data signal (SD), used to transmit audio data in two's complement format 59 | * @return true on success, false on error 60 | */ 61 | bool initI2S(int _bclk, int _lrclk, int _din); 62 | 63 | /** 64 | * @fn initBluetooth 65 | * @brief Initialize bluetooth 66 | * @param _btName - The created Bluetooth device name 67 | * @return true on success, false on error 68 | */ 69 | bool initBluetooth(const char * _btName); 70 | 71 | /** 72 | * @fn initSDCard 73 | * @brief Initialize SD card 74 | * @param csPin The number of the cs pin for spi communication of SD card module 75 | * @return true on success, false on error 76 | */ 77 | bool initSDCard(uint8_t csPin=GPIO_NUM_5); 78 | 79 | /*************************** Function ******************************/ 80 | 81 | /** 82 | * @fn scanSDMusic 83 | * @brief Scan the music files in WAV format in the SD card 84 | * @param musicList - The music files in WAV format scanned from the SD card. Type: character string array. 85 | * @return None 86 | * @note Only support English for path name of music files and WAV for their format currently. 87 | */ 88 | void scanSDMusic(String * musicList); 89 | 90 | /** 91 | * @fn playSDMusic 92 | * @brief Play music files in the SD card 93 | * @param Filename - music file name, only support the music files in .wav format currently 94 | * @note Music file name must be an absolute path like /musicDir/music.wav 95 | * @return None 96 | * @note Only support English for path name of music files and WAV for their format currently. 97 | */ 98 | void playSDMusic(const char *Filename); 99 | 100 | /** 101 | * @fn SDPlayerControl 102 | * @brief SD card music playback control interface 103 | * @param CMD - playback control command: 104 | * @n SD_AMPLIFIER_PLAY: start to play music, which can be played from the position where you paused before 105 | * @n If no music file is selected through playSDMusic(), the first one in the playlist will be played by default. 106 | * @n Playback error may occur if music files are not scanned from SD card in the correct format (only support English for path name of music files and WAV for their format currently) 107 | * @n SD_AMPLIFIER_PAUSE: pause playback, retain the playback position of the current music file 108 | * @n SD_AMPLIFIER_STOP: stop playback, end the current music playback 109 | * @return None 110 | */ 111 | void SDPlayerControl(uint8_t CMD); 112 | 113 | /** 114 | * @fn getMetadata 115 | * @brief Get "metadata" through AVRC command 116 | * @param type - The type of metadata to be obtained, and the parameters currently supported: 117 | * @n ESP_AVRC_MD_ATTR_TITLE ESP_AVRC_MD_ATTR_ARTIST ESP_AVRC_MD_ATTR_ALBUM 118 | * @return The corresponding type of "metadata" 119 | */ 120 | String getMetadata(uint8_t type); 121 | 122 | /** 123 | * @fn getRemoteAddress 124 | * @brief Get address of Bluetooth device remotely 125 | * @note The address will be obtained after the module is paired with the remote Bluetooth device and successfully communicates with it based on the Bluetooth AVRCP protocol. 126 | * @return Return the array pointer storing the address of the remote Bluetooth device 127 | * @n Return None when the module does not connect to the remote device or failed to communicate with it based on the Bluetooth AVRCP protocol. 128 | * @n AVRCP(Audio Video Remote Control Profile) 129 | */ 130 | uint8_t * getRemoteAddress(void); 131 | 132 | /** 133 | * @fn setVolume 134 | * @brief Set volume 135 | * @param vol - Set volume, the range can be set to 0-9 136 | * @note 5 for the original volume of audio data, no increase or decrease 137 | * @return None 138 | */ 139 | void setVolume(float vol); 140 | 141 | /** 142 | * @fn openFilter 143 | * @brief Enable audio filter 144 | * @param type - bq_type_highpass: enable high-pass filtering; bq_type_lowpass: enable low-pass filtering 145 | * @param fc - Threshold of filtering, range: 2-20000 146 | * @note For example, setting high-pass filter mode and the threshold of 500 indicates to filter out the audio signal below 500; high-pass filter and low-pass filter will work simultaneously. 147 | * @return None 148 | */ 149 | void openFilter(int type, float fc); 150 | 151 | /** 152 | * @fn closeFilter 153 | * @brief Disable the audio filter 154 | * @return None 155 | */ 156 | void closeFilter(void); 157 | 158 | /** 159 | * @fn reverseLeftRightChannels 160 | * @brief Reverse left and right channels, When you find that the left 161 | * @n and right channels play opposite, you can call this interface to adjust 162 | * @return None 163 | */ 164 | void reverseLeftRightChannels(void); 165 | 166 | ``` 167 | 168 | 169 | ## Compatibility 170 | 171 | MCU | Work Well | Work Wrong | Untested | Remarks 172 | ------------------ | :----------: | :----------: | :---------: | :----: 173 | FireBeetle-ESP32 | √ | | | 174 | 175 | 176 | ## History 177 | 178 | - 2022/02/07 - Version 1.0.0 released. 179 | - 2022/09/21 - Version 1.0.1 released. 180 | 181 | 182 | ## Credits 183 | 184 | Written by qsjhyy(yihuan.huang@dfrobot.com), 2022. (Welcome to our [website](https://www.dfrobot.com/)) 185 | 186 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | # DFRobot_MAX98357A 2 | * [English Version](./README.md) 3 | 4 | 这是一个I2S功放模块。体积小巧,使用简单。可快速的将ESP32主控做成蓝牙音箱、或者WIFI对讲机, 5 | 语音识别、网络播报设备等。也可以加上SD卡模块播放SD卡音乐。 6 | 7 | ![产品实物图](./resources/images/MAX98357A.png) 8 | 9 | 10 | ## 产品链接 (https://www.dfrobot.com.cn/goods-3573.html) 11 | SKU: DFR0954 12 | 13 | 14 | ## 目录 15 | 16 | * [概述](#概述) 17 | * [库安装](#库安装) 18 | * [方法](#方法) 19 | * [兼容性](#兼容性) 20 | * [历史](#历史) 21 | * [创作者](#创作者) 22 | 23 | 24 | ## 概述 25 | 26 | * 可直插,可贴片。小巧方便。 27 | * 板载I2S功放,3W功率音质好。 28 | 29 | 30 | ## 库安装 31 | 32 | 要使用这个库, 首先下载库文件, 将其粘贴到\Arduino\libraries目录中, 然后打开示例文件夹并在文件夹中运行演示。 33 | 34 | 35 | ## 方法 36 | 37 | ```C++ 38 | 39 | /** 40 | * @fn begin 41 | * @brief Init function 42 | * @param btName - 创建的蓝牙设备名 43 | * @param bclk - I2S通信引脚号, 串行时钟SCK, 也叫位时钟(BCK) 44 | * @param lrclk - I2S通信引脚号, 帧时钟WS, 即命令(声道)选择,用于切换左右声道的数据 45 | * @param din - I2S通信引脚号, 串行数据信号SD, 用于传输二进制补码表示的音频数据 46 | * @return true on success, false on error 47 | */ 48 | bool begin(const char *btName="bluetoothAmplifier", 49 | int bclk=GPIO_NUM_25, 50 | int lrclk=GPIO_NUM_26, 51 | int din=GPIO_NUM_27); 52 | 53 | /** 54 | * @fn initI2S 55 | * @brief Initialize I2S 56 | * @param _bclk - I2S通信引脚号, 串行时钟SCK, 也叫位时钟(BCK) 57 | * @param _lrclk - I2S通信引脚号, 帧时钟WS, 即命令(声道)选择,用于切换左右声道的数据 58 | * @param _din - I2S通信引脚号, 串行数据信号SD, 用于传输二进制补码表示的音频数据 59 | * @return true on success, false on error 60 | */ 61 | bool initI2S(int _bclk, int _lrclk, int _din); 62 | 63 | /** 64 | * @fn initBluetooth 65 | * @brief Initialize bluetooth 66 | * @param _btName - 创建的蓝牙设备名 67 | * @return true on success, false on error 68 | */ 69 | bool initBluetooth(const char * _btName); 70 | 71 | /** 72 | * @fn initSDCard 73 | * @brief Initialize SD card 74 | * @param csPin SD卡模块的spi通信的cs引脚号 75 | * @return true on success, false on error 76 | */ 77 | bool initSDCard(uint8_t csPin=GPIO_NUM_5); 78 | 79 | /*************************** 功能函数 ******************************/ 80 | 81 | /** 82 | * @fn scanSDMusic 83 | * @brief 扫描SD卡里面的WAV格式的音乐文件 84 | * @param musicList - SD卡里面扫描到的WAV格式的音乐文件, 类型是字符串数组 85 | * @return None 86 | * @note 音乐文件路径名字当前仅支持英文, 格式当前仅支持WAV格式的音乐文件 87 | */ 88 | void scanSDMusic(String * musicList); 89 | 90 | /** 91 | * @fn playSDMusic 92 | * @brief 播放SD卡里面的音频文件 93 | * @param Filename - 音乐文件名, 当前仅支持 .wav 格式的音频文件 94 | * @note 音乐文件名需为绝对路径, 列如: /musicDir/music.wav 95 | * @return None 96 | * @note 音乐文件路径名字当前仅支持英文, 格式当前仅支持WAV格式的音乐文件 97 | */ 98 | void playSDMusic(const char *Filename); 99 | 100 | /** 101 | * @fn SDPlayerControl 102 | * @brief SD卡音频播放控制接口 103 | * @param CMD - 播放控制命令: 104 | * @n SD_AMPLIFIER_PLAY: 开始播放音乐, 可从之前暂停播放的位置继续播放 105 | * @n 若没有通过playSDMusic()选择播放的音乐文件, 会默认播放音乐列表第一首 106 | * @n 若SD卡没有扫描到正确格式的音乐文件也会播放失败(音乐文件路径名字当前仅支持英文, 格式当前仅支持WAV格式的音乐文件) 107 | * @n SD_AMPLIFIER_PAUSE: 暂停播放, 保留当前音乐文件的播放位置 108 | * @n SD_AMPLIFIER_STOP: 停止播放, 结束当前音乐的播放 109 | * @return None 110 | */ 111 | void SDPlayerControl(uint8_t CMD); 112 | 113 | /** 114 | * @fn getMetadata 115 | * @brief 通过 AVRC 命令获取"诠释数据"(metadata) 116 | * @param type - 需要获取的元数据的类型, 目前支持的参数: 117 | * @n ESP_AVRC_MD_ATTR_TITLE ESP_AVRC_MD_ATTR_ARTIST ESP_AVRC_MD_ATTR_ALBUM 118 | * @return 相应类型的 "元数据" 119 | */ 120 | String getMetadata(uint8_t type); 121 | 122 | /** 123 | * @fn getRemoteAddress 124 | * @brief 获取远程蓝牙设备地址 125 | * @note 地址将在本机与远程蓝牙设备配对连接后, 成功进行蓝牙AVRCP协议通信后获取 126 | * @return 返回存储远程蓝牙设备地址的数组指针 127 | * @n 当未连接远程设备, 或和远程设备进行蓝牙AVRCP协议通信不成功时, 返回None 128 | * @n AVRCP(Audio Video Remote Control Profile) 129 | */ 130 | uint8_t * getRemoteAddress(void); 131 | 132 | /** 133 | * @fn setVolume 134 | * @brief 设置音量 135 | * @param vol - 设置音量, 可以设置范围 0 ~ 9 136 | * @note 5 即为音频数据原始音量, 无增减 137 | * @return None 138 | */ 139 | void setVolume(float vol); 140 | 141 | /** 142 | * @fn openFilter 143 | * @brief 打开音频滤波器 144 | * @param type - bq_type_highpass: 开启高通滤波; bq_type_lowpass: 开启低通滤波 145 | * @param fc - 过滤波的阈值, 范围: 2~20000 146 | * @note 列如, 设置高通滤波模式, 阈值为500, 即为过滤掉音频信号中低于500的信号; 且高通滤波和低通滤波会同时工作 147 | * @return None 148 | */ 149 | void openFilter(int type, float fc); 150 | 151 | /** 152 | * @fn closeFilter 153 | * @brief 关闭音频滤波器 154 | * @return None 155 | */ 156 | void closeFilter(void); 157 | 158 | /** 159 | * @fn reverseLeftRightChannels 160 | * @brief Reverse left and right channels, When you find that the left 161 | * @n and right channels play opposite, you can call this interface to adjust 162 | * @return None 163 | */ 164 | void reverseLeftRightChannels(void); 165 | 166 | ``` 167 | 168 | 169 | ## 兼容性 170 | 171 | MCU | Work Well | Work Wrong | Untested | Remarks 172 | ------------------ | :----------: | :----------: | :---------: | :----: 173 | FireBeetle-ESP32 | √ | | | 174 | 175 | 176 | ## 历史 177 | 178 | - 2022/02/07 - 1.0.0 版本 179 | - 2022/09/21 - 1.0.1 版本 180 | 181 | 182 | ## 创作者 183 | 184 | Written by qsjhyy(yihuan.huang@dfrobot.com), 2022. (Welcome to our [website](https://www.dfrobot.com/)) 185 | 186 | -------------------------------------------------------------------------------- /examples/SDmusic/SDmusic.ino: -------------------------------------------------------------------------------- 1 | /*! 2 | * @file SDmusic.ino 3 | * @brief This demo allows you to play the audio in the SD card (currently only support the audio in .wav format) 4 | * @details Connect the module with Dupont wires or other cables as per the comments of init function below, and insert the SD card 5 | * @n If there are music files in the correct format in the SD card, 6 | * @n (only support English for music file and path name currently and try not to use spaces, only support .wav for the audio format currently) 7 | * @n you can set volume, set filter, scan, play, pause and switch songs through the config function in setup 8 | * @n and adjust the config through the serial command when the program is running as per the code comment in the loop function. 9 | * @note The serial operation can be directly changed to other operations such as button or knob in practice so as to realize real Bluetooth speaker. 10 | * @copyright Copyright (c) 2010 DFRobot Co.Ltd (http://www.dfrobot.com) 11 | * @license The MIT License (MIT) 12 | * @author [qsjhyy](yihuan.huang@dfrobot.com) 13 | * @version V1.0 14 | * @date 2022-02-16 15 | * @url https://github.com/DFRobot/DFRobot_MAX98357A 16 | */ 17 | #include 18 | 19 | DFRobot_MAX98357A amplifier; // instantiate an object to control the amplifier 20 | 21 | String musicList[100]; // SD card music list 22 | 23 | /************************************************************** 24 | Setup And Loop 25 | **************************************************************/ 26 | 27 | void setup(void) 28 | { 29 | Serial.begin(115200); 30 | 31 | /** 32 | * @brief Initialize I2S 33 | * @param _bclk - I2S communication pin number, serial clock (SCK), aka bit clock (BCK) 34 | * @param _lrclk - I2S communication pin number, word select (WS), i.e. command (channel) select, used to switch between left and right channel data 35 | * @param _din - I2S communication pin number, serial data signal (SD), used to transmit audio data in two's complement format 36 | * @return true on success, false on error 37 | */ 38 | while ( !amplifier.initI2S(/*_bclk=*/GPIO_NUM_25, /*_lrclk=*/GPIO_NUM_26, /*_din=*/GPIO_NUM_27) ){ 39 | Serial.println("Initialize I2S failed !"); 40 | delay(3000); 41 | } 42 | /** 43 | * @fn initSDCard 44 | * @brief Initialize SD card 45 | * @param csPin cs pin number for spi communication of SD card module 46 | * @return true on success, false on error 47 | * @note Pin connection of SD card module 48 | * @n SD Card | ESP32 49 | * @n +5V | VCC(5V) 50 | * @n GND | GND 51 | * @n SS | csPin (default to be IO5, can be customized through init function) 52 | * @n SCK | SCK(IO18) 53 | * @n MISO | MISO(IO19) 54 | * @n MOSI | MOSI(IO23) 55 | * @n Search MicroSD card reader module at www.dfrobot.com 56 | */ 57 | while (!amplifier.initSDCard(/*csPin=*/GPIO_NUM_5)){ 58 | Serial.println("Initialize SD card failed !"); 59 | delay(3000); 60 | } 61 | Serial.println("Initialize succeed!"); 62 | 63 | /** 64 | * @fn reverseLeftRightChannels 65 | * @brief Reverse left and right channels, When you find that the left 66 | * @n and right channels play opposite, you can call this interface to adjust 67 | */ 68 | // amplifier.reverseLeftRightChannels(); 69 | 70 | /** 71 | * @brief Scan the music files in WAV format in the SD card 72 | * @param musicList - The music files in .wav format scanned from the SD card. Type is character string array. 73 | * @return None 74 | * @note Only support English for music file and path name currently and try not to use spaces, only support .wav for the audio format currently 75 | */ 76 | amplifier.scanSDMusic(musicList); 77 | /** 78 | * Print the list of the scanned music files that can be played 79 | */ 80 | printMusicList(); 81 | 82 | /** 83 | * @brief Set volume 84 | * @param vol - Set volume, the range can be set to 0-9 85 | * @note 5 for the original volume of audio data, no increase or decrease 86 | */ 87 | amplifier.setVolume(5); 88 | 89 | /** 90 | * @brief Close the audio filter 91 | * @note The high-pass filter and the low-pass filter will be closed at the same time because they work simultaneously. 92 | * @n When you set the range outstripping the value people's ears can distinguish, it is considered to close the corresponding filter; 93 | * @n The initial values of the filters are bq_type_highpass(2) and bq_type_lowpass(20000) 94 | * @n The filter is considered to be off at this time. 95 | */ 96 | amplifier.closeFilter(); 97 | 98 | /** 99 | * @brief Open audio filter 100 | * @param type - bq_type_highpass: open high-pass filtering; bq_type_lowpass: open low-pass filtering 101 | * @param fc - Threshold of filtering, range: 2-20000 102 | * @note For example, setting high-pass filter mode and the threshold of 500 indicates to filter out the audio signal below 500; high-pass filter and low-pass filter will work simultaneously. 103 | close the audio filter 104 | */ 105 | amplifier.openFilter(bq_type_highpass, 500); 106 | 107 | /** 108 | * @brief SD card music playback control interface 109 | * @param CMD - Playback control command: 110 | * @n SD_AMPLIFIER_PLAY: Start to play music, which can be played from the position where you paused before 111 | * @n If no music file is selected through playSDMusic(), the first one in the list will be played by default. 112 | * @n Playback error may occur if music files are not scanned from SD card in the correct format (only support English for path name of music files and WAV for their format currently) 113 | * @n SD_AMPLIFIER_PAUSE: Pause playback, keep the playback position of the current music file 114 | * @n SD_AMPLIFIER_STOP: Stop playback, end the current music playback 115 | * @return None 116 | */ 117 | amplifier.SDPlayerControl(SD_AMPLIFIER_PLAY); 118 | delay(5000); // Play for 5 seconds first, clearly separated from the next operation of skipping the song 119 | 120 | /** 121 | * @brief Play music files in the SD card 122 | * @param Filename - music file name, only support the music files in .wav format currently 123 | * @note Music file name must be an absolute path like /musicDir/music.wav 124 | * @return None 125 | */ 126 | if(musicList[1].length()){ 127 | Serial.println("Changing Music...\n"); 128 | amplifier.playSDMusic(musicList[1].c_str()); 129 | }else{ 130 | Serial.println("The currently selected music file is incorrect!\n"); 131 | } 132 | 133 | } 134 | 135 | void loop() 136 | { 137 | /** 138 | * @brief The parsing and implementation of the serial command is to call the config function above when the program is running using the serial command 139 | * @note The amplifier can be configured by entering the corresponding command in the serial port, format is: cmd-value (command - set value, some commands that don't require assignment can be omitted) 140 | * @n Currently available commands: 141 | * @n Start playback: e.g. start- 142 | * @n Pause playback: e.g. pause- 143 | * @n Stop playback: e.g. stop- 144 | * @n Print music list: e.g. musicList- 145 | * @n Change songs according to the music list: e.g. changeMusic-1 146 | * @n Set and open high-pass filter: e.g. hp-500 147 | * @n Set and open low-pass filter: e.g. lp-15000 148 | * @n Close filter: e.g. closeFilter- 149 | * @n Set volume: e.g. vol-5.0 150 | * @n Other commands will print this description 151 | * @n For the detailed meaning of the commands, please refer to the comments of the corresponding functions. 152 | */ 153 | parseSerialCommand(); 154 | delay(500); 155 | } 156 | 157 | /************************************************************** 158 | Print the list of the scanned music files that can be played 159 | **************************************************************/ 160 | 161 | void printMusicList(void) 162 | { 163 | uint8_t i = 0; 164 | 165 | if(musicList[i].length()){ 166 | Serial.println("\nMusic List: "); 167 | }else{ 168 | Serial.println("The SD card audio file scan is empty, please check whether there are audio files in the SD card that meet the format!"); 169 | } 170 | 171 | while(musicList[i].length()){ 172 | Serial.print("\t"); 173 | Serial.print(i); 174 | Serial.print(" - "); 175 | Serial.println(musicList[i]); 176 | i++; 177 | } 178 | } 179 | 180 | /************************************************************** 181 | The parsing and implementation of the serial command 182 | **************************************************************/ 183 | 184 | void parseSerialCommand(void) 185 | { 186 | String cmd; // Save the command type read in the serial port 187 | float value; // Save the command value read in the serial port 188 | 189 | /** 190 | * Command format: cmd-value 191 | * cmd : indicate the command type 192 | * value : indicate the set value corresponding to the command type, some commands can be empty 193 | * For example: (1) set high-pass filter, filter the audio data below 500: hp-500 194 | * (2) close filter: closeFilter- 195 | */ 196 | if(Serial.available()){ // Detect whether there is an available serial command 197 | cmd = Serial.readStringUntil('-'); // Read the specified terminator character string, used to cut and identify the serial command. The same comment won't repeat later. 198 | 199 | if(cmd.equals("hp")){ // Determine if it’s the command type for setting high-pass filter 200 | Serial.println("Setting a High-Pass filter...\n"); 201 | value =Serial.parseFloat(); // Parse character string and return floating point number 202 | 203 | /** 204 | * @brief Open audio filter 205 | * @param type - bq_type_highpass: open high-pass filtering; bq_type_lowpass: open low-pass filtering 206 | * @param fc - Threshold of filtering, range: 2-20000 207 | * @note For example, setting high-pass filter mode and the threshold of 500 indicates to filter out the audio signal below 500; high-pass filter and low-pass filter can work simultaneously. 208 | */ 209 | amplifier.openFilter(bq_type_highpass, value); 210 | 211 | 212 | }else if(cmd.equals("lp")){ // Determine if it's the command type for setting low-pass filter 213 | Serial.println("Setting a Low-Pass filter...\n"); 214 | value =Serial.parseFloat(); 215 | 216 | amplifier.openFilter(bq_type_lowpass, value); 217 | 218 | }else if(cmd.equals("closeFilter")){ // Determine if it's the command type for closing filter 219 | Serial.println("Closing filter...\n"); 220 | 221 | /** 222 | * @brief Close the audio filter 223 | */ 224 | amplifier.closeFilter(); 225 | 226 | }else if(cmd.equals("vol")){ // Determine if it's the command type for setting volume 227 | Serial.println("Setting volume...\n"); 228 | value =Serial.parseFloat(); 229 | 230 | /** 231 | * @brief Set volume 232 | * @param vol - Set volume, the range can be set to 0-9 233 | * @note 5 for the original volume of audio data, no increase or decrease 234 | */ 235 | amplifier.setVolume(value); 236 | 237 | }else if(cmd.equals("start")){ // Determine if it's the command type for starting playback 238 | Serial.println("starting amplifier...\n"); 239 | 240 | /** 241 | * @brief SD card music playback control interface 242 | * @param CMD - Playback control command: 243 | * @n SD_AMPLIFIER_PLAY: Start to play music, which can be played from the position where you paused before 244 | * @n If no music file is selected through playSDMusic(), the first one in the list will be played by default. 245 | * @n Playback error may occur if music files are not scanned from SD card in the correct format (only support English for path name of music files and WAV for their format currently) 246 | * @n SD_AMPLIFIER_PAUSE: pause playback, keep the playback position of the current music file 247 | * @n SD_AMPLIFIER_STOP: stop playback, end the current music playback 248 | * @return None 249 | */ 250 | amplifier.SDPlayerControl(SD_AMPLIFIER_PLAY); 251 | 252 | }else if(cmd.equals("pause")){ // Determine if it's the command type for pausing playback 253 | Serial.println("Pause amplifier...\n"); 254 | 255 | // The same as above 256 | amplifier.SDPlayerControl(SD_AMPLIFIER_PAUSE); 257 | 258 | }else if(cmd.equals("stop")){ // Determine if it's the command type for stopping playback 259 | Serial.println("Stopping amplifier...\n"); 260 | 261 | // The same as above 262 | amplifier.SDPlayerControl(SD_AMPLIFIER_STOP); 263 | 264 | }else if(cmd.equals("musicList")){ // Determine if it's the command type for printing the list of the music files that can be played currently 265 | Serial.println("Scanning music list...\n"); 266 | 267 | /** 268 | * @brief Scan the music files in WAV format in the SD card 269 | * @param musicList - The music files in .wav format scanned from the SD card. Type is character string array 270 | * @return None 271 | * @note Only support English for music file and path name currently and try to avoid spaces, only support .wav for the audio format currently 272 | */ 273 | amplifier.scanSDMusic(musicList); 274 | /** 275 | * Print the list of the scanned music files that can be played 276 | */ 277 | printMusicList(); 278 | 279 | }else if(cmd.equals("changeMusic")){ // Determine if it's the command type for changing songs according to the music list 280 | cmd = musicList[Serial.parseInt()]; 281 | 282 | /** 283 | * @brief Play music files in the SD card 284 | * @param Filename - music file name, only support the music files in .wav format currently 285 | * @note Music file name must be an absolute path like /musicDir/music.wav 286 | * @return None 287 | */ 288 | if(cmd.length()){ 289 | Serial.println("Changing Music...\n"); 290 | amplifier.playSDMusic(cmd.c_str()); 291 | }else{ 292 | Serial.println("The currently selected music file is incorrect!\n"); 293 | } 294 | 295 | }else{ // Unknown command type 296 | Serial.println("Help : \n \ 297 | Currently available commands (format: cmd-value):\n \ 298 | Start playback: e.g. start-\n \ 299 | Pause playback: e.g. pause-\n \ 300 | Stop playback: e.g. stop-\n \ 301 | Print music list: e.g. musicList-\n \ 302 | Change songs according to the music list: e.g. changeMusic-1\n \ 303 | Set and open high-pass filter: e.g. hp-500\n \ 304 | Set and open low-pass filter: e.g. lp-15000\n \ 305 | Close filter: e.g. closeFilter-\n \ 306 | Set volume: e.g. vol-5.0\n \ 307 | For the detailed meaning, please refer to the code comments of this demo.\n"); // 308 | } 309 | while(Serial.read() >= 0); // Clear the remaining data in the serial port 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /examples/bluetoothAmplifier/bluetoothAmplifier.ino: -------------------------------------------------------------------------------- 1 | /*! 2 | * @file bluetoothAmplifier.ino 3 | * @brief Simple Bluetooth Audio Player 4 | * @details When your other Bluetooth devices (e.g. cellphone) is successfully connected to the ESP32 controller that has burnt this sample 5 | * @n and the amplifier has been properly connected to ESP32 through the I2S communication pin, 6 | * @n you got a simple Bluetooth speaker, you can hear the music playing in your phone from the speaker connected to the amplifier, 7 | * @n and you can get some information about the song currently playing through the Bluetooth AVRCP protocol command. 8 | * @copyright Copyright (c) 2010 DFRobot Co.Ltd (http://www.dfrobot.com) 9 | * @license The MIT License (MIT) 10 | * @author [qsjhyy](yihuan.huang@dfrobot.com) 11 | * @version V1.0 12 | * @date 2022-01-24 13 | * @url https://github.com/DFRobot/DFRobot_MAX98357A 14 | */ 15 | #include 16 | 17 | DFRobot_MAX98357A amplifier; // instantiate an object to control the amplifier 18 | 19 | void setup(void) 20 | { 21 | Serial.begin(115200); 22 | 23 | /** 24 | * @brief Init function 25 | * @param btName - The created Bluetooth device name 26 | * @param bclk - I2S communication pin number, serial clock (SCK), aka bit clock (BCK) 27 | * @param lrclk - I2S communication pin number, word select (WS), i.e. command (channel) select, used to switch between left and right channel data 28 | * @param din - I2S communication pin number, serial data signal (SD), used to transmit audio data in two's complement format 29 | * @return true on success, false on error 30 | */ 31 | while( !amplifier.begin(/*btName=*/"bluetoothAmplifier", /*bclk=*/GPIO_NUM_25, /*lrclk=*/GPIO_NUM_26, /*din=*/GPIO_NUM_27) ){ 32 | Serial.println("Initialize failed !"); 33 | delay(3000); 34 | } 35 | Serial.println("Initialize succeed!"); 36 | 37 | } 38 | 39 | void loop(void) 40 | { 41 | String Title, Artist, Album; 42 | /** 43 | * @brief Get "metadata" through AVRC command 44 | * @param type - The type of metadata to be obtained, and the parameters currently supported: 45 | * @n ESP_AVRC_MD_ATTR_TITLE ESP_AVRC_MD_ATTR_ARTIST ESP_AVRC_MD_ATTR_ALBUM 46 | * @return The corresponding type of "metadata" 47 | * @note Return "NULL" if timeout occurs when requesting metadata 48 | */ 49 | Title = amplifier.getMetadata(ESP_AVRC_MD_ATTR_TITLE); 50 | if(0 != Title.length()){ 51 | Serial.print("Music title: "); 52 | Serial.println(Title); 53 | } 54 | Artist = amplifier.getMetadata(ESP_AVRC_MD_ATTR_ARTIST); 55 | if(0 != Artist.length()){ 56 | Serial.print("Music artist: "); 57 | Serial.println(Artist); 58 | } 59 | Album = amplifier.getMetadata(ESP_AVRC_MD_ATTR_ALBUM); 60 | if(0 != Album.length()){ 61 | Serial.print("Music album: "); 62 | Serial.println(Album); 63 | } 64 | delay(3000); 65 | } 66 | -------------------------------------------------------------------------------- /examples/configAmplifier/configAmplifier.ino: -------------------------------------------------------------------------------- 1 | /*! 2 | * @file configAmplifier.ino 3 | * @brief Simply configure the Bluetooth amplifier 4 | * @details After reading the bluetoothAmplifier.ino sample, you may succeed in playing music. 5 | * @n This demo allows you to simply configure the player: set volume, set filter, get the address of the remote Bluetooth device 6 | * @n You can configure it through the setup function, and can also adjust the config through the serial command when the program is running as per the code comment in the loop function. 7 | * @note The serial operation can be directly changed to other operations such as button or knob in practice so as to realize a real Bluetooth speaker. 8 | * @copyright Copyright (c) 2010 DFRobot Co.Ltd (http://www.dfrobot.com) 9 | * @license The MIT License (MIT) 10 | * @author [qsjhyy](yihuan.huang@dfrobot.com) 11 | * @version V1.0 12 | * @date 2022-01-27 13 | * @url https://github.com/DFRobot/DFRobot_MAX98357A 14 | */ 15 | #include 16 | 17 | DFRobot_MAX98357A amplifier; // instantiate an object to control the amplifier 18 | 19 | /************************************************************** 20 | Setup And Loop 21 | **************************************************************/ 22 | 23 | void setup(void) 24 | { 25 | Serial.begin(115200); 26 | 27 | /** 28 | * @brief Init function 29 | * @param btName - The created Bluetooth device name 30 | * @param bclk - I2S communication pin number, serial clock (SCK), aka bit clock (BCK) 31 | * @param lrclk - I2S communication pin number, word select (WS), i.e. command (channel) select, used to switch between left and right channel data 32 | * @param din - I2S communication pin number, serial data signal (SD), used to transmit audio data in two's complement format 33 | * @return true on success, false on error 34 | */ 35 | while( !amplifier.begin(/*btName=*/"bluetoothAmplifier", /*bclk=*/GPIO_NUM_25, /*lrclk=*/GPIO_NUM_26, /*din=*/GPIO_NUM_27) ){ 36 | Serial.println("Initialize failed !"); 37 | delay(3000); 38 | } 39 | Serial.println("Initialize succeed!"); 40 | 41 | /** 42 | * @fn reverseLeftRightChannels 43 | * @brief Reverse left and right channels, When you find that the left 44 | * @n and right channels play opposite, you can call this interface to adjust 45 | */ 46 | // amplifier.reverseLeftRightChannels(); 47 | 48 | /** 49 | * @brief Set volume 50 | * @param vol - Set volume, the range can be set to 0-9 51 | * @note 5 for the original volume of audio data, no increase or decrease 52 | */ 53 | amplifier.setVolume(5); 54 | 55 | /** 56 | * @brief Close the audio filter 57 | * @note The high-pass filter and the low-pass filter will be closed at the same time because they work simultaneously, 58 | * @n When you set the range outstripping the value people's ears can distinguish, it is considered to close the corresponding filter; 59 | * @n The initial values of the filters are bq_type_highpass(2) and bq_type_lowpass(20000), 60 | * @n The filter is considered to be off at this time. 61 | */ 62 | amplifier.closeFilter(); 63 | 64 | /** 65 | * @brief Open audio filter 66 | * @param type - bq_type_highpass: open high-pass filtering; bq_type_lowpass: open low-pass filtering 67 | * @param fc - Threshold of filtering, range: 2-20000 68 | * @note For example, setting high-pass filter mode and the threshold of 500 indicates to filter out the audio signal below 500; high-pass filter and low-pass filter will work simultaneously. 69 | */ 70 | amplifier.openFilter(bq_type_highpass, 500); 71 | 72 | /** 73 | * @brief Get address of remote Bluetooth device 74 | * @note The address will be obtained after the module is paired with the remote Bluetooth device and successfully communicates with it based on the Bluetooth AVRCP protocol. 75 | * @return Return the array pointer storing the address of the remote Bluetooth device 76 | * @n Return None when the module does not connect to the remote device or failed to communicate with it based on the Bluetooth AVRCP protocol. 77 | * @n AVRCP(Audio Video Remote Control Profile) 78 | */ 79 | uint8_t * addr = amplifier.getRemoteAddress(); 80 | /** 81 | * Print the obtained address of the remote Bluetooth device 82 | * Note: if the remote Bluetooth address is not obtained, it keeps waiting. 83 | * The function implementation follows this demo, you can change it as per your need. 84 | */ 85 | printRemoteAddress(addr); 86 | 87 | /** 88 | * @brief Send passthrough command to AVRCP target. 89 | * @param[in] tl : transaction label, 0 to 15, consecutive commands should use different values. 90 | * @param[in] key_code : passthrough command code, e.g. 91 | * @n ESP_AVRC_PT_CMD_PLAY : start playback; 92 | * @n ESP_AVRC_PT_CMD_STOP : stop playback; 93 | * @n ESP_AVRC_PT_CMD_BACKWARD : play the previous one; 94 | * @n ESP_AVRC_PT_CMD_FORWARD : play the next one; etc. 95 | * @param[in] key_state : passthrough command key state, ESP_AVRC_PT_CMD_STATE_PRESSED or ESP_AVRC_PT_CMD_STATE_RELEASED 96 | * @note This function may not be so practical, but you can try it if you are interested. For the details refer to: 97 | * @n https://github.com/espressif/esp-idf/blob/master/components/bt/host/bluedroid/api/include/api/esp_avrc_api.h 98 | */ 99 | esp_avrc_ct_send_passthrough_cmd(/*tl=*/0, /*tl=key_code*/ESP_AVRC_PT_CMD_PLAY, /*tl=key_state*/ESP_AVRC_PT_CMD_STATE_PRESSED); 100 | 101 | } 102 | 103 | void loop() 104 | { 105 | /** 106 | * @brief The parsing and implementation of the serial command is to call the config function above by the serial command when the program is running 107 | * @note The amplifier can be configured by entering the corresponding command in the serial port, format: cmd-value (command - set value, some commands that don't require assignment can be omitted) 108 | * @n Currently available commands: 109 | * @n Set and open high-pass filter: e.g. hp-500 110 | * @n Set and open low-pass filter: e.g. lp-15000 111 | * @n Close filter: e.g. closeFilter- 112 | * @n Set volume: e.g. vol-5 113 | * @n Get the address of the remote Bluetooth device: e.g. addr- 114 | * @n Start playback: e.g. start- 115 | * @n Stop playback: e.g. stop- 116 | * @n Play last track: e.g. previous- 117 | * @n Play next track: e.g. next- 118 | * @n Other commands will print this description 119 | * @n For the detailed meaning of the commands, please refer to the comments of the corresponding functions. 120 | */ 121 | parseSerialCommand(); 122 | delay(500); 123 | } 124 | 125 | /************************************************************** 126 | Print the obtained remote Bluetooth address 127 | **************************************************************/ 128 | 129 | void printRemoteAddress(uint8_t * _addr) 130 | { 131 | while(NULL == _addr){ // When obtaining remote Bluetooth address failed, it will wait until it succeeds. 132 | Serial.println("Please connect the remote Bluetooth device!"); 133 | delay(5000); 134 | _addr = amplifier.getRemoteAddress(); // Get the remote Bluetooth address again. 135 | } 136 | Serial.print("Remote bluetooth device address : "); 137 | for(uint8_t i=0; i<5; i++){ 138 | Serial.print(*_addr, HEX); 139 | Serial.print("-"); 140 | _addr++; 141 | } 142 | Serial.println(*_addr, HEX); 143 | Serial.println(); 144 | } 145 | 146 | /************************************************************** 147 | The parsing and implementation of the serial command 148 | **************************************************************/ 149 | 150 | void parseSerialCommand(void) 151 | { 152 | String cmd; // Save the command type read in the serial port 153 | float value; // Save the command value read in the serial port 154 | 155 | /** 156 | * Command format: cmd-value 157 | * cmd: command type 158 | * value: the set value corresponding to the command type, some commands can be empty 159 | * For example: (1) set high-pass filter, filter the audio data below 500: hp-500 160 | * (2) close filter: closeFilter- 161 | */ 162 | if(Serial.available()){ // Detect whether there is an available serial command 163 | cmd = Serial.readStringUntil('-'); // Read the specified termination character string, used to cut and identify the serial command. The same type won't repeat later. 164 | 165 | if(cmd.equals("hp")){ // Determine if it's the command type for setting high-pass filter 166 | Serial.println("Setting a High-Pass filter...\n"); 167 | value =Serial.parseFloat(); // Parse character string and return floating point number 168 | /** 169 | * @brief Open audio filter 170 | * @param type - bq_type_highpass: open high-pass filtering; bq_type_lowpass: open low-pass filtering 171 | * @param fc - Threshold of filtering, range: 2-20000 172 | * @note For example, setting high-pass filter mode and the threshold of 500 indicates to filter out the audio signal below 500; high-pass filter and low-pass filter can work simultaneously. 173 | */ 174 | amplifier.openFilter(bq_type_highpass, value); 175 | 176 | }else if(cmd.equals("lp")){ // Determine if it's the command type for setting low-pass filter 177 | Serial.println("Setting a Low-Pass filter...\n"); 178 | value = Serial.parseFloat(); 179 | amplifier.openFilter(bq_type_lowpass, value); 180 | 181 | }else if(cmd.equals("closeFilter")){ // Determine if it's the command type for closing filter 182 | Serial.println("Closing filter...\n"); 183 | /** 184 | * @brief Close the audio filter 185 | */ 186 | amplifier.closeFilter(); 187 | 188 | }else if(cmd.equals("vol")){ // Determine if it's the command type for setting volume 189 | Serial.println("Setting volume...\n"); 190 | value = Serial.parseFloat(); 191 | /** 192 | * @brief Set volume 193 | * @param vol - Set volume, the range can be set to 0-9 194 | * @note 5 for the original volume of audio data, no increase or decrease 195 | */ 196 | amplifier.setVolume(value); 197 | 198 | }else if(cmd.equals("addr")){ // Determine if it's the command type for obtaining remote Bluetooth address 199 | /** 200 | * @brief Get the remote Bluetooth address 201 | * @note The address will be obtained after the module is paired with the remote Bluetooth device and successfully communicates with it based on the Bluetooth AVRCP protocol. 202 | * @return Return the array pointer storing the address of the remote Bluetooth device 203 | * @n Return None when the module does not connect to the remote device or failed to communicate with it based on the Bluetooth AVRCP protocol. 204 | * @n AVRCP(Audio Video Remote Control Profile) 205 | */ 206 | uint8_t * addr = amplifier.getRemoteAddress(); 207 | /** 208 | * Print the obtained address of the remote Bluetooth device 209 | * Note: if the remote Bluetooth address is not obtained, it keeps waiting. 210 | * The function implementation follows this demo, you can change it as per your need. 211 | */ 212 | printRemoteAddress(addr); 213 | 214 | }else if(cmd.equals("start")){ // Determine if it's the command type for starting playback 215 | Serial.println("starting amplifier...\n"); 216 | /** 217 | * @brief Send passthrough command to AVRCP target. 218 | * @param[in] tl : transaction label, 0 to 15, consecutive commands should use different values. 219 | * @param[in] key_code : passthrough command code, e.g. ESP_AVRC_PT_CMD_PLAY, ESP_AVRC_PT_CMD_STOP, etc. 220 | * @param[in] key_state : passthrough command key state, ESP_AVRC_PT_CMD_STATE_PRESSED or ESP_AVRC_PT_CMD_STATE_RELEASED 221 | * @note This function may not be so practical, but you can try it if you are interested. For the details refer to: 222 | * @n https://github.com/espressif/esp-idf/blob/master/components/bt/host/bluedroid/api/include/api/esp_avrc_api.h 223 | */ 224 | esp_avrc_ct_send_passthrough_cmd(0, ESP_AVRC_PT_CMD_PLAY, ESP_AVRC_PT_CMD_STATE_PRESSED); 225 | 226 | }else if(cmd.equals("stop")){ // Determine if it's the command type for stopping playback 227 | Serial.println("Stopping amplifier...\n"); 228 | // Same as the above. 229 | esp_avrc_ct_send_passthrough_cmd(0, ESP_AVRC_PT_CMD_STOP, ESP_AVRC_PT_CMD_STATE_PRESSED); 230 | 231 | }else if(cmd.equals("previous")){ // Determine if it's the command type for playing last track 232 | Serial.println("Playing previous...\n"); 233 | // Same as the above. 234 | esp_avrc_ct_send_passthrough_cmd(0, ESP_AVRC_PT_CMD_BACKWARD, ESP_AVRC_PT_CMD_STATE_PRESSED); 235 | 236 | }else if(cmd.equals("next")){ // Determine if it's the command type for playing next track 237 | Serial.println("Playing next...\n"); 238 | // Same as the above. 239 | esp_avrc_ct_send_passthrough_cmd(0, ESP_AVRC_PT_CMD_FORWARD, ESP_AVRC_PT_CMD_STATE_PRESSED); 240 | 241 | }else{ // Unknown command type 242 | Serial.println("Help : \n \ 243 | Currently available commands (format: cmd-value):\n \ 244 | Set and open high-pass filter: e.g. hp-500\n \ 245 | Set and open low-pass filter: e.g. lp-15000\n \ 246 | Close filter: e.g. closeFilter-\n \ 247 | Set volume: e.g. vol-5.0\n \ 248 | Get the address of the remote Bluetooth device: e.g. addr-\n \ 249 | Start playback: e.g. start-\n \ 250 | Stop playback: e.g. stop-\n \ 251 | Play last track: e.g. previous-\n \ 252 | Play next track: e.g. next-\n \ 253 | For more details about commands, please refer to the code comments of this demo.\n"); // 254 | } 255 | while(Serial.read() >= 0); // Clear the remaining data in the serial port 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /examples/customBluetooth/customBluetooth.ino: -------------------------------------------------------------------------------- 1 | /*! 2 | * @file customBluetooth.ino 3 | * @brief Completely customize Bluetooth config and use. Codes more prone to being called by underlying API(Application Programming Interface). 4 | * @details Try configuring the callback functions related to Bluetooth to improve your Bluetooth speaker! 5 | * @note This demo has the same function as bluetoothAmplifier.ino when there is nothing modified. 6 | * @copyright Copyright (c) 2010 DFRobot Co.Ltd (http://www.dfrobot.com) 7 | * @license The MIT License (MIT) 8 | * @author [qsjhyy](yihuan.huang@dfrobot.com) 9 | * @version V1.0 10 | * @date 2022-01-27 11 | * @url https://github.com/DFRobot/DFRobot_MAX98357A 12 | */ 13 | #include 14 | 15 | #include // Bluetooth 16 | #include 17 | #include 18 | #include // Bluetooth A2DP protocol 19 | #include // Bluetooth AVRC protocol 20 | 21 | #include // I2S communication 22 | 23 | 24 | uint8_t remoteAddress[6]; // address of the connected remote Bluetooth device 25 | 26 | float _volume = 1.0; // change the coefficient of audio signal volume 27 | int32_t _sampleRate = 44100; // I2S communication frequency 28 | bool _avrcConnected = false; // AVRC connection status 29 | 30 | String _metadata = ""; // metadata 31 | uint8_t _metaFlag = 0; // metadata refresh flag 32 | 33 | /************************************************************** 34 | Setup And Loop 35 | **************************************************************/ 36 | 37 | void setup(void) 38 | { 39 | Serial.begin(115200); 40 | 41 | // Initialize I2S 42 | if (!initI2S(/*_bclk=*/GPIO_NUM_25, /*_lrclk=*/GPIO_NUM_26, /*_din=*/GPIO_NUM_27)){ 43 | Serial.println("Initialize I2S failed !"); 44 | } 45 | 46 | // Initialize bluetooth 47 | if (!initBluetooth("counstomAmplifier")){ 48 | Serial.println("Initialize bluetooth failed !"); 49 | } 50 | 51 | } 52 | 53 | void loop() 54 | { 55 | // 详细参见: https://github.com/espressif/esp-idf/blob/master/components/bt/host/bluedroid/api/include/api/esp_avrc_api.h 56 | esp_avrc_ct_send_metadata_cmd(6, ESP_AVRC_MD_ATTR_TITLE); // request metadata from remote Bluetooth device via AVRC command 57 | delay(3000); 58 | } 59 | 60 | /************************************************************** 61 | Init 62 | **************************************************************/ 63 | 64 | bool initI2S(int _bclk, int _lrclk, int _din) 65 | { 66 | static const i2s_config_t i2s_config = { 67 | .mode = static_cast(I2S_MODE_MASTER | I2S_MODE_TX), //Work as host, only transmit data 68 | .sample_rate = _sampleRate, 69 | .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, // 16 bits per sample 70 | .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, // 2-channels 71 | .communication_format = I2S_COMM_FORMAT_STAND_I2S, // I2S communication I2S Philips standard, data launch at second BCK 72 | .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, // Interrupt level 1 73 | .dma_buf_count = 4, // number of buffers, 128 max. 74 | .dma_buf_len = 400, // size of each buffer, AVRC communication may be affected if the value is too high. 75 | .use_apll = false, // For the application of a high precision clock, select the APLL_CLK clock source in the frequency range of 16 to 128 MHz. It’s not the case here, so select false. 76 | .tx_desc_auto_clear = true 77 | }; 78 | 79 | static const i2s_pin_config_t pin_config = { 80 | .bck_io_num = _bclk, // serial clock (SCK), aka bit clock (BCK) 81 | .ws_io_num = _lrclk, // word select (WS), i.e. command (channel) select, used to switch between left and right channel data 82 | .data_out_num = _din, // serial data signal (SD), used to transmit audio data in two's complement format 83 | .data_in_num = I2S_PIN_NO_CHANGE // Not used 84 | }; 85 | 86 | if (i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL)){ 87 | Serial.println("Install and start I2S driver failed !"); 88 | return false; 89 | } 90 | if (i2s_set_pin(I2S_NUM_0, &pin_config)){ 91 | Serial.println("Set I2S pin number failed !"); 92 | return false; 93 | } 94 | 95 | return true; 96 | } 97 | 98 | bool initBluetooth(const char * _btName) 99 | { 100 | // Initialize bluedroid 101 | if (!btStarted() && !btStart()){ 102 | Serial.println("Initialize controller failed"); 103 | return false; 104 | } 105 | esp_bluedroid_status_t bt_state = esp_bluedroid_get_status(); 106 | if (bt_state == ESP_BLUEDROID_STATUS_UNINITIALIZED){ 107 | if (esp_bluedroid_init()) { 108 | Serial.println("Initialize bluedroid failed !"); 109 | return false; 110 | } 111 | } 112 | if (bt_state != ESP_BLUEDROID_STATUS_ENABLED){ 113 | if (esp_bluedroid_enable()) { 114 | Serial.println("Enable bluedroid failed !"); 115 | return false; 116 | } 117 | } 118 | if (esp_bt_dev_set_device_name(_btName)){ 119 | Serial.println("Set device name failed !"); 120 | return false; 121 | } 122 | 123 | // Initialize AVRCP 124 | if (esp_avrc_ct_init()){ 125 | Serial.println("Initialize the bluetooth AVRCP controller module failed !"); 126 | return false; 127 | } 128 | if (esp_avrc_ct_register_callback(avrcCallback)){ // non-essential, callback for Bluetooth AVRC protocol 129 | Serial.println("Register application callbacks to AVRCP module failed !"); 130 | return false; 131 | } 132 | 133 | // Initialize A2DP 134 | if (esp_a2d_sink_init()){ 135 | Serial.println("Initialize the bluetooth A2DP sink module failed !"); 136 | return false; 137 | } 138 | if (esp_a2d_register_callback(a2dpCallback)){ // non-essential, callback for Bluetooth A2DP protocol 139 | Serial.println("Register application callbacks to A2DP module failed !"); 140 | return false; 141 | } 142 | if (esp_a2d_sink_register_data_callback(audioDataProcessCallback)){ // required, callback for processing data received by Bluetooth 143 | Serial.println("Register A2DP sink data output function failed !"); 144 | return false; 145 | } 146 | 147 | 148 | // Set discoverability and connectability mode for legacy bluetooth. 149 | if (esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE)){ 150 | Serial.println("Set discoverability and connectability mode for legacy bluetooth failed !"); 151 | return false; 152 | } 153 | 154 | return true; 155 | } 156 | 157 | /************************************************************** 158 | Callback Function 159 | **************************************************************/ 160 | 161 | // This callback function is required to transfer the audio stream data received by Bluetooth to the amplifier via I2S, the audio stream data can be processed here. 162 | // Refer to: https://github.com/espressif/esp-idf/blob/master/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/main.c 163 | void audioDataProcessCallback(const uint8_t *data, uint32_t len) 164 | { 165 | int16_t* data16 = (int16_t*)data; // convert to 16-bit sample data 166 | int16_t processedData; // store the processed audio data 167 | int count = len/2; // the number of audio data in int16_t to be processed 168 | size_t i2s_bytes_write = 0; // i2s_write() the variable storing the number of data to be written 169 | 170 | for(int i=0; imeta_rsp.attr_length + 1); 188 | memcpy(attr_text, rc->meta_rsp.attr_text, rc->meta_rsp.attr_length); 189 | attr_text[rc->meta_rsp.attr_length] = 0; 190 | _metadata = String(attr_text); 191 | _metaFlag = rc->meta_rsp.attr_id; 192 | Serial.print("_metadata : "); 193 | Serial.println(_metadata); 194 | // Serial.println(rc->meta_rsp.attr_id); 195 | 196 | free(attr_text); 197 | break; 198 | } 199 | /*!< connection state changed event */ 200 | case ESP_AVRC_CT_CONNECTION_STATE_EVT:{ 201 | /*!< connection established */ 202 | _avrcConnected = rc->conn_stat.connected; 203 | if(_avrcConnected){ 204 | uint8_t * p = rc->conn_stat.remote_bda; 205 | Serial.print("remoteAddress : "); 206 | for(uint8_t i=0; i<6; i++){ 207 | remoteAddress[i] = *(p + i); 208 | Serial.print(remoteAddress[i], HEX); 209 | Serial.print("-"); 210 | } 211 | /*!< disconnecting remote device */ 212 | }else{ 213 | memset(remoteAddress, 0, 6); 214 | } 215 | break; 216 | } 217 | /*!< passthrough response event */ 218 | case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT: 219 | /*!< notification event */ 220 | case ESP_AVRC_CT_CHANGE_NOTIFY_EVT: 221 | /*!< feature of remote device indication event */ 222 | case ESP_AVRC_CT_REMOTE_FEATURES_EVT: 223 | /*!< supported notification events capability of peer device */ 224 | case ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT: 225 | /*!< play status response event */ 226 | case ESP_AVRC_CT_PLAY_STATUS_RSP_EVT: 227 | /*!< set absolute volume response event */ 228 | case ESP_AVRC_CT_SET_ABSOLUTE_VOLUME_RSP_EVT: 229 | break; 230 | default: 231 | // "unhandled AVRC event: %d", event 232 | break; 233 | } 234 | } 235 | 236 | // Non-required, callback for Bluetooth A2DP protocol, some events are triggered by sending the corresponding command. 237 | void a2dpCallback(esp_a2d_cb_event_t event, esp_a2d_cb_param_t*param) 238 | { 239 | esp_a2d_cb_param_t *a2d = (esp_a2d_cb_param_t *)(param); 240 | 241 | switch (event) { 242 | /*!< 243 | * Audio codec is configured, only used for A2DP SINK 244 | * 245 | * @brief ESP_A2D_AUDIO_CFG_EVT 246 | * 247 | * struct a2d_audio_cfg_param { 248 | * esp_bd_addr_t remote_bda; /*!< remote bluetooth device address 249 | * esp_a2d_mcc_t mcc; /*!< A2DP media codec capability information 250 | * } audio_cfg; /*!< media codec configuration information 251 | */ 252 | case ESP_A2D_AUDIO_CFG_EVT: 253 | /*!< 254 | * Connection state changed event 255 | * 256 | * @brief ESP_A2D_CONNECTION_STATE_EVT 257 | * 258 | * struct a2d_conn_stat_param { 259 | * esp_a2d_connection_state_t state; /*!< one of values from esp_a2d_connection_state_t 260 | * esp_bd_addr_t remote_bda; /*!< remote bluetooth device address 261 | * esp_a2d_disc_rsn_t disc_rsn; /*!< reason of disconnection for "DISCONNECTED" 262 | * } conn_stat; /*!< A2DP connection status 263 | */ 264 | case ESP_A2D_CONNECTION_STATE_EVT: 265 | /*!< audio stream transmission state changed event */ 266 | case ESP_A2D_AUDIO_STATE_EVT: 267 | /*!< acknowledge event in response to media control commands */ 268 | case ESP_A2D_MEDIA_CTRL_ACK_EVT: 269 | /*!< indicate a2dp init&deinit complete */ 270 | case ESP_A2D_PROF_STATE_EVT: 271 | break; 272 | default: 273 | // "a2dp invalid cb event: %d", event 274 | break; 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map For DFRobot_MAX98357A 3 | ####################################### 4 | 5 | ####################################### 6 | # Datatypes (KEYWORD1) 7 | ####################################### 8 | 9 | DFRobot_MAX98357A KEYWORD1 10 | Biquad KEYWORD1 11 | 12 | ####################################### 13 | # Methods and Functions (KEYWORD2) 14 | ####################################### 15 | 16 | begin KEYWORD2 17 | initI2S KEYWORD2 18 | initBluetooth KEYWORD2 19 | initSDCard KEYWORD2 20 | 21 | scanSDMusic KEYWORD2 22 | playSDMusic KEYWORD2 23 | SDPlayerControl KEYWORD2 24 | 25 | getMetadata KEYWORD2 26 | getRemoteAddress KEYWORD2 27 | 28 | setVolume KEYWORD2 29 | openFilter KEYWORD2 30 | closeFilter KEYWORD2 31 | 32 | ####################################### 33 | # Constants (LITERAL1) 34 | ####################################### 35 | 36 | I2S_NUM_0 LITERAL1 37 | NUMBER_OF_FILTER LITERAL1 38 | SD_AMPLIFIER_PLAY LITERAL1 39 | SD_AMPLIFIER_PAUSE LITERAL1 40 | SD_AMPLIFIER_STOP LITERAL1 41 | bq_type_highpass LITERAL1 42 | bq_type_lowpass LITERAL1 43 | ESP_AVRC_MD_ATTR_TITLE LITERAL1 44 | ESP_AVRC_MD_ATTR_ARTIST LITERAL1 45 | ESP_AVRC_MD_ATTR_ALBUM LITERAL1 46 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=DFRobot_MAX98357A 2 | version=1.0.1 3 | author=DFRobot 4 | maintainer=qsjhyy 5 | sentence=This is a Library for MAX98357A(SKU: DFR0954). 6 | paragraph=Play the music through Bluetooth or SD card, and then do some simple processing to the audio stream data, finally transmitted to the I2S power MAX98357A equipment to complete the audio playback. 7 | category=Audio 8 | url=https://github.com/DFRobot/DFRobot_MAX98357A 9 | architectures=esp32 10 | -------------------------------------------------------------------------------- /resources/images/MAX98357A.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFRobot/DFRobot_MAX98357A/0401ffc21e12945ff4c6c575790d88878e719688/resources/images/MAX98357A.png -------------------------------------------------------------------------------- /src/Biquad.cpp: -------------------------------------------------------------------------------- 1 | /*! 2 | * Thanks Nigel Redmon a lot 3 | * Note: The corresponding file is adapted from the source code. For the source code, please refer to the following website. 4 | */ 5 | 6 | // 7 | // Biquad.cpp 8 | // 9 | // Created by Nigel Redmon on 11/24/12 10 | // EarLevel Engineering: earlevel.com 11 | // Copyright 2012 Nigel Redmon 12 | // 13 | // For a complete explanation of the Biquad code: 14 | // http://www.earlevel.com/main/2012/11/26/biquad-c-source-code/ 15 | // 16 | // License: 17 | // 18 | // This source code is provided as is, without warranty. 19 | // You may copy and distribute verbatim copies of this document. 20 | // You may modify and use this source code to create binary code 21 | // for your own purposes, free or commercial. 22 | // 23 | 24 | #include 25 | #include "Biquad.h" 26 | 27 | Biquad::Biquad() { 28 | type = bq_type_lowpass; 29 | a0 = 1.0; 30 | a1 = a2 = b1 = b2 = 0.0; 31 | Fc = 0.50; 32 | Q = 0.707; 33 | peakGain = 0.0; 34 | z1 = z2 = 0.0; 35 | } 36 | 37 | Biquad::Biquad(int type, float Fc, float Q, float peakGainDB) { 38 | setBiquad(type, Fc, Q, peakGainDB); 39 | // z1 = z2 = 0.0; 40 | } 41 | 42 | Biquad::~Biquad() { 43 | } 44 | 45 | void Biquad::setType(int type) { 46 | this->type = type; 47 | calcBiquad(); 48 | } 49 | 50 | void Biquad::setQ(float Q) { 51 | this->Q = Q; 52 | calcBiquad(); 53 | } 54 | 55 | void Biquad::setFc(float Fc) { 56 | this->Fc = Fc; 57 | calcBiquad(); 58 | } 59 | 60 | void Biquad::setPeakGain(float peakGainDB) { 61 | this->peakGain = peakGainDB; 62 | calcBiquad(); 63 | } 64 | 65 | void Biquad::setBiquad(int type, float Fc, float Q, float peakGainDB) { 66 | this->type = type; 67 | this->Q = Q; 68 | this->Fc = Fc; 69 | setPeakGain(peakGainDB); 70 | z1 = z2 = 0.0; 71 | } 72 | 73 | void Biquad::calcBiquad(void) { 74 | float norm; 75 | float V = pow(10, fabs(peakGain) / 20.0); 76 | float K = tan(PI * Fc); 77 | switch (this->type) { 78 | case bq_type_lowpass: 79 | norm = 1 / (1 + K / Q + K * K); 80 | a0 = K * K * norm; 81 | a1 = 2 * a0; 82 | a2 = a0; 83 | b1 = 2 * (K * K - 1) * norm; 84 | b2 = (1 - K / Q + K * K) * norm; 85 | break; 86 | 87 | case bq_type_highpass: 88 | norm = 1 / (1 + K / Q + K * K); 89 | a0 = 1 * norm; 90 | a1 = -2 * a0; 91 | a2 = a0; 92 | b1 = 2 * (K * K - 1) * norm; 93 | b2 = (1 - K / Q + K * K) * norm; 94 | break; 95 | 96 | case bq_type_bandpass: 97 | norm = 1 / (1 + K / Q + K * K); 98 | a0 = K / Q * norm; 99 | a1 = 0; 100 | a2 = -a0; 101 | b1 = 2 * (K * K - 1) * norm; 102 | b2 = (1 - K / Q + K * K) * norm; 103 | break; 104 | 105 | case bq_type_notch: 106 | norm = 1 / (1 + K / Q + K * K); 107 | a0 = (1 + K * K) * norm; 108 | a1 = 2 * (K * K - 1) * norm; 109 | a2 = a0; 110 | b1 = a1; 111 | b2 = (1 - K / Q + K * K) * norm; 112 | break; 113 | 114 | case bq_type_peak: 115 | if (peakGain >= 0) { // boost 116 | norm = 1 / (1 + 1/Q * K + K * K); 117 | a0 = (1 + V/Q * K + K * K) * norm; 118 | a1 = 2 * (K * K - 1) * norm; 119 | a2 = (1 - V/Q * K + K * K) * norm; 120 | b1 = a1; 121 | b2 = (1 - 1/Q * K + K * K) * norm; 122 | } 123 | else { // cut 124 | norm = 1 / (1 + V/Q * K + K * K); 125 | a0 = (1 + 1/Q * K + K * K) * norm; 126 | a1 = 2 * (K * K - 1) * norm; 127 | a2 = (1 - 1/Q * K + K * K) * norm; 128 | b1 = a1; 129 | b2 = (1 - V/Q * K + K * K) * norm; 130 | } 131 | break; 132 | case bq_type_lowshelf: 133 | if (peakGain >= 0) { // boost 134 | norm = 1 / (1 + sqrt(2) * K + K * K); 135 | a0 = (1 + sqrt(2*V) * K + V * K * K) * norm; 136 | a1 = 2 * (V * K * K - 1) * norm; 137 | a2 = (1 - sqrt(2*V) * K + V * K * K) * norm; 138 | b1 = 2 * (K * K - 1) * norm; 139 | b2 = (1 - sqrt(2) * K + K * K) * norm; 140 | } 141 | else { // cut 142 | norm = 1 / (1 + sqrt(2*V) * K + V * K * K); 143 | a0 = (1 + sqrt(2) * K + K * K) * norm; 144 | a1 = 2 * (K * K - 1) * norm; 145 | a2 = (1 - sqrt(2) * K + K * K) * norm; 146 | b1 = 2 * (V * K * K - 1) * norm; 147 | b2 = (1 - sqrt(2*V) * K + V * K * K) * norm; 148 | } 149 | break; 150 | case bq_type_highshelf: 151 | if (peakGain >= 0) { // boost 152 | norm = 1 / (1 + sqrt(2) * K + K * K); 153 | a0 = (V + sqrt(2*V) * K + K * K) * norm; 154 | a1 = 2 * (K * K - V) * norm; 155 | a2 = (V - sqrt(2*V) * K + K * K) * norm; 156 | b1 = 2 * (K * K - 1) * norm; 157 | b2 = (1 - sqrt(2) * K + K * K) * norm; 158 | } 159 | else { // cut 160 | norm = 1 / (V + sqrt(2*V) * K + K * K); 161 | a0 = (1 + sqrt(2) * K + K * K) * norm; 162 | a1 = 2 * (K * K - 1) * norm; 163 | a2 = (1 - sqrt(2) * K + K * K) * norm; 164 | b1 = 2 * (K * K - V) * norm; 165 | b2 = (V - sqrt(2*V) * K + K * K) * norm; 166 | } 167 | break; 168 | } 169 | 170 | return; 171 | } 172 | -------------------------------------------------------------------------------- /src/Biquad.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * Thanks Nigel Redmon a lot 3 | * Note: The corresponding file is adapted from the source code. For the source code, please refer to the following website. 4 | */ 5 | 6 | // 7 | // Biquad.h 8 | // 9 | // Created by Nigel Redmon on 11/24/12 10 | // EarLevel Engineering: earlevel.com 11 | // Copyright 2012 Nigel Redmon 12 | // 13 | // For a complete explanation of the Biquad code: 14 | // http://www.earlevel.com/main/2012/11/25/biquad-c-source-code/ 15 | // 16 | // License: 17 | // 18 | // This source code is provided as is, without warranty. 19 | // You may copy and distribute verbatim copies of this document. 20 | // You may modify and use this source code to create binary code 21 | // for your own purposes, free or commercial. 22 | // 23 | 24 | #ifndef Biquad_h 25 | #define Biquad_h 26 | 27 | #include 28 | 29 | /** 30 | * @enum None 31 | * @brief 7 filter modes 32 | */ 33 | enum { 34 | bq_type_lowpass = 0, 35 | bq_type_highpass, 36 | bq_type_bandpass, 37 | bq_type_notch, 38 | bq_type_peak, 39 | bq_type_lowshelf, 40 | bq_type_highshelf 41 | }; 42 | 43 | class Biquad { 44 | public: 45 | /** 46 | * @fn Biquad 47 | * @brief Constructor 48 | * @param type - Filter type select, as the enumerated type above 49 | * @param Fc - Ratio of filter threshold to sampling frequency, range: 0.0-0.5 50 | * @param Q - Filter coefficient 51 | * @param peakGainDB - Peak gain. It is required in some filter modes. 52 | * @return None 53 | */ 54 | Biquad(); 55 | Biquad(int type, float Fc, float Q, float peakGainDB); 56 | ~Biquad(); 57 | 58 | /** 59 | * @fn setType 60 | * @brief Set filter type 61 | * @param type - Filter type select, as the enumerated type above 62 | * @return None 63 | */ 64 | void setType(int type); 65 | 66 | /** 67 | * @fn setQ 68 | * @brief Set filter coefficient 69 | * @param Q - Filter coefficient 70 | * @return None 71 | */ 72 | void setQ(float Q); 73 | 74 | /** 75 | * @fn setFc 76 | * @brief Ratio of filter threshold to sampling frequency 77 | * @param Fc - Ratio of filter threshold to sampling frequency, range: 0.0-0.5 78 | * @return None 79 | */ 80 | void setFc(float Fc); 81 | 82 | /** 83 | * @fn setPeakGain 84 | * @brief Set peak gain 85 | * @param peakGainDB - Peak gain. It is required in some filter modes. 86 | * @return None 87 | */ 88 | void setPeakGain(float peakGainDB); 89 | 90 | /** 91 | * @fn setBiquad 92 | * @brief Set all the parameters of the filter 93 | * @param type - Filter type select, as the enumerated type above 94 | * @param Fc - Ratio of filter threshold to sampling frequency, range: 0.0-0.5 95 | * @param Q - Filter coefficient 96 | * @param peakGainDB - Peak gain. It is required in some filter modes 97 | * @return None 98 | */ 99 | void setBiquad(int type, float Fc, float Q, float peakGainDB); 100 | 101 | /** 102 | * @fn process 103 | * @brief Process the input data according to calculated parameters 104 | * @param in - Data to be processed 105 | * @return The processed data 106 | */ 107 | float process(float in); 108 | 109 | protected: 110 | 111 | /** 112 | * @fn calcBiquad 113 | * @brief Calculate the final data processing coefficient according to the parameters configured above 114 | * @return None 115 | */ 116 | void calcBiquad(void); 117 | 118 | int type; 119 | float a0, a1, a2, b1, b2; 120 | float Fc, Q, peakGain; 121 | float z1, z2; 122 | }; 123 | 124 | inline float Biquad::process(float in) { 125 | float out = in * a0 + z1; 126 | z1 = in * a1 + z2 - b1 * out; 127 | z2 = in * a2 - b2 * out; 128 | return out; 129 | } 130 | 131 | #endif // Biquad_h 132 | -------------------------------------------------------------------------------- /src/DFRobot_MAX98357A.cpp: -------------------------------------------------------------------------------- 1 | /*! 2 | * @file DFRobot_MAX98357A.cpp 3 | * @brief Define the infrastructure DFRobot_MAX98357A class 4 | * @details Configure a classic Bluetooth, pair with Bluetooth devices, receive Bluetooth audio, 5 | * @n Process simple audio signal, and pass it into the amplifier using I2S communication 6 | * @copyright Copyright (c) 2010 DFRobot Co.Ltd (http://www.dfrobot.com) 7 | * @license The MIT License (MIT) 8 | * @author [qsjhyy](yihuan.huang@dfrobot.com) 9 | * @version V1.0 10 | * @date 2022-01-21 11 | * @url https://github.com/DFRobot/DFRobot_MAX98357A 12 | */ 13 | #include "DFRobot_MAX98357A.h" 14 | 15 | uint8_t DFRobot_MAX98357A::remoteAddress[6]; // Address of the connected remote Bluetooth device 16 | 17 | float _volume = 1.0; // Change the coefficient of audio signal volume 18 | int32_t _sampleRate = 44100; // I2S communication frequency 19 | bool _avrcConnected = false; // AVRC connection status 20 | bool _filterFlag = false; // Filter enabling flag 21 | 22 | String _metadata = ""; // metadata 23 | uint8_t _metaFlag = 0; // metadata refresh flag 24 | uint8_t _voiceSource = MAX98357A_VOICE_FROM_BT; // The audio source, used to correct left and right audio 25 | 26 | Biquad _filterLLP[NUMBER_OF_FILTER]; // Left channel low-pass filter 27 | Biquad _filterRLP[NUMBER_OF_FILTER]; // Right channel low-pass filter 28 | Biquad _filterLHP[NUMBER_OF_FILTER]; // Left channel high-pass filter 29 | Biquad _filterRHP[NUMBER_OF_FILTER]; // Right channel high-pass filter 30 | 31 | char fileName[100]; 32 | uint8_t SDAmplifierMark = SD_AMPLIFIER_STOP; // SD card play flag 33 | xTaskHandle xPlayWAV = NULL; // SD card play Task 34 | String _musicList[100]; // SD card music list 35 | uint8_t musicCount = 0; // SD card music count 36 | 37 | /** 38 | * @struct sWavParse_t 39 | * @brief The struct for parsing audio information in WAV format 40 | */ 41 | typedef struct 42 | { 43 | char riffType[4]; 44 | unsigned int riffSize; 45 | char waveType[4]; 46 | char formatType[4]; 47 | unsigned int formatSize; 48 | uint16_t compressionCode; 49 | i2s_channel_t numChannels; 50 | uint32_t sampleRate; 51 | unsigned int bytesPerSecond; 52 | unsigned short blockAlign; 53 | i2s_bits_per_sample_t bitsPerSample; 54 | char dataType1[1]; 55 | char dataType2[3]; 56 | unsigned int dataSize; 57 | char data[800]; 58 | }sWavParse_t; 59 | 60 | /** 61 | * @struct sWavInfo_t 62 | * @brief All the information of the audio in WAV format 63 | */ 64 | typedef struct 65 | { 66 | sWavParse_t header; 67 | FILE *fp; 68 | }sWavInfo_t; 69 | 70 | /*************************** Init ******************************/ 71 | 72 | DFRobot_MAX98357A::DFRobot_MAX98357A() 73 | { 74 | } 75 | 76 | bool DFRobot_MAX98357A::begin(const char *btName, int bclk, int lrclk, int din) 77 | { 78 | // Initialize I2S 79 | if (!initI2S(bclk, lrclk, din)){ 80 | DBG("Initialize I2S failed !"); 81 | return false; 82 | } 83 | 84 | // Initialize bluetooth 85 | if (!initBluetooth(btName)){ 86 | DBG("Initialize bluetooth failed !"); 87 | return false; 88 | } 89 | 90 | // Initialize the filter 91 | setFilter(_filterLLP, bq_type_lowpass, 20000.0); 92 | setFilter(_filterRLP, bq_type_lowpass, 20000.0); 93 | setFilter(_filterLHP, bq_type_highpass, 2.0); 94 | setFilter(_filterRHP, bq_type_highpass, 2.0); 95 | 96 | return true; 97 | } 98 | 99 | void DFRobot_MAX98357A::end(void) 100 | { 101 | ESP_ERROR_CHECK(esp_avrc_ct_deinit()); // destroy AVRCP 102 | ESP_ERROR_CHECK(esp_a2d_sink_deinit()); // destroy A2DP 103 | ESP_ERROR_CHECK(esp_bluedroid_disable()); // stop & destroy bluetooth 104 | ESP_ERROR_CHECK(esp_bluedroid_deinit()); 105 | btStop(); 106 | ESP_ERROR_CHECK(i2s_driver_uninstall(I2S_NUM_0)); // stop & destroy i2s driver 107 | } 108 | 109 | bool DFRobot_MAX98357A::initI2S(int _bclk, int _lrclk, int _din) 110 | { 111 | static const i2s_config_t i2s_config = { 112 | .mode = static_cast(I2S_MODE_MASTER | I2S_MODE_TX), // The main controller can transmit data but not receive. 113 | .sample_rate = _sampleRate, 114 | .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, // 16 bits per sample 115 | .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, // 2-channels 116 | .communication_format = I2S_COMM_FORMAT_STAND_I2S, // I2S communication I2S Philips standard, data launch at second BCK 117 | .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, // Interrupt level 1 118 | .dma_buf_count = 4, // number of buffers, 128 max. 119 | .dma_buf_len = 400, // size of each buffer, AVRC communication may be affected if the value is too high. 120 | .use_apll = false, // For the application of a high precision clock, select the APLL_CLK clock source in the frequency range of 16 to 128 MHz. It's not the case here, so select false. 121 | .tx_desc_auto_clear = true 122 | }; 123 | 124 | static const i2s_pin_config_t pin_config = { 125 | .bck_io_num = _bclk, // Serial clock (SCK), aka bit clock (BCK) 126 | .ws_io_num = _lrclk, // Word select (WS), i.e. command (channel) select, used to switch between left and right channel data 127 | .data_out_num = _din, // Serial data signal (SD), used to transmit audio data in two's complement format 128 | .data_in_num = I2S_PIN_NO_CHANGE // Not used 129 | }; 130 | 131 | if (i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL)){ 132 | DBG("Install and start I2S driver failed !"); 133 | return false; 134 | } 135 | if (i2s_set_pin(I2S_NUM_0, &pin_config)){ 136 | DBG("Set I2S pin number failed !"); 137 | return false; 138 | } 139 | 140 | return true; 141 | } 142 | 143 | bool DFRobot_MAX98357A::initBluetooth(const char * _btName) 144 | { 145 | // Initialize bluedroid 146 | if (!btStarted() && !btStart()){ 147 | DBG("Initialize controller failed"); 148 | return false; 149 | } 150 | esp_bluedroid_status_t bt_state = esp_bluedroid_get_status(); 151 | if (bt_state == ESP_BLUEDROID_STATUS_UNINITIALIZED){ 152 | if (esp_bluedroid_init()) { 153 | DBG("Initialize bluedroid failed !"); 154 | return false; 155 | } 156 | } 157 | if (bt_state != ESP_BLUEDROID_STATUS_ENABLED){ 158 | if (esp_bluedroid_enable()) { 159 | DBG("Enable bluedroid failed !"); 160 | return false; 161 | } 162 | } 163 | if (esp_bt_dev_set_device_name(_btName)){ 164 | DBG("Set device name failed !"); 165 | return false; 166 | } 167 | 168 | // Initialize AVRCP 169 | if (esp_avrc_ct_init()){ 170 | DBG("Initialize the bluetooth AVRCP controller module failed !"); 171 | return false; 172 | } 173 | if (esp_avrc_ct_register_callback(avrcCallback)){ 174 | DBG("Register application callbacks to AVRCP module failed !"); 175 | return false; 176 | } 177 | 178 | // Initialize A2DP 179 | if (esp_a2d_sink_init()){ 180 | DBG("Initialize the bluetooth A2DP sink module failed !"); 181 | return false; 182 | } 183 | if (esp_a2d_register_callback(a2dpCallback)){ 184 | DBG("Register application callbacks to A2DP module failed !"); 185 | return false; 186 | } 187 | if (esp_a2d_sink_register_data_callback(audioDataProcessCallback)){ 188 | DBG("Register A2DP sink data output function failed !"); 189 | return false; 190 | } 191 | 192 | // Set discoverability and connectability mode for legacy bluetooth. 193 | if (esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE)){ 194 | DBG("Set discoverability and connectability mode for legacy bluetooth failed !"); 195 | return false; 196 | } 197 | _voiceSource = MAX98357A_VOICE_FROM_BT; 198 | 199 | return true; 200 | } 201 | 202 | bool DFRobot_MAX98357A::initSDCard(uint8_t csPin) 203 | { 204 | if(!SD.begin(csPin)){ 205 | DBG("Card Mount Failed"); 206 | return false; 207 | } 208 | 209 | uint8_t cardType = SD.cardType(); 210 | if(cardType == CARD_NONE){ 211 | DBG("No SD card attached"); 212 | return false; 213 | } 214 | 215 | DBG("SD Card Type: "); 216 | if(cardType == CARD_MMC){ 217 | DBG("MMC"); 218 | } else if(cardType == CARD_SD){ 219 | DBG("SDSC"); 220 | } else if(cardType == CARD_SDHC){ 221 | DBG("SDHC"); 222 | } else { 223 | DBG("UNKNOWN"); 224 | } 225 | 226 | uint64_t cardSize = SD.cardSize() / (1024 * 1024); 227 | DBG("SD Card Size: "); 228 | DBG(cardSize); 229 | // Serial.printf("SD Card Size: %lluMB\n", cardSize); 230 | 231 | _voiceSource = MAX98357A_VOICE_FROM_SD; 232 | 233 | SDAmplifierMark = SD_AMPLIFIER_STOP; 234 | xTaskCreate(&playWAV, "playWAV", 2048, NULL, 5, &xPlayWAV); 235 | 236 | return true; 237 | } 238 | 239 | /*************************** Function ******************************/ 240 | 241 | void DFRobot_MAX98357A::reverseLeftRightChannels(void) 242 | { 243 | _voiceSource = (_voiceSource ? MAX98357A_VOICE_FROM_SD : MAX98357A_VOICE_FROM_BT); 244 | } 245 | 246 | void DFRobot_MAX98357A::listDir(fs::FS &fs, const char * dirName) 247 | { 248 | DBG(dirName); 249 | DBG("|"); 250 | 251 | File root = fs.open(dirName); 252 | if(!root){ 253 | DBG("Failed to open directory"); 254 | return; 255 | } 256 | if(!root.isDirectory()){ 257 | DBG("Not a directory"); 258 | return; 259 | } 260 | 261 | File file = root.openNextFile(); 262 | while(file){ 263 | // DBG(file.name()); 264 | if(file.isDirectory()){ 265 | listDir(fs, file.path()); 266 | } else { 267 | DBG(file.path()); 268 | if(strstr(file.name(), ".wav")){ 269 | _musicList[musicCount] = file.path(); 270 | musicCount++; 271 | } 272 | } 273 | file = root.openNextFile(); 274 | } 275 | } 276 | 277 | void DFRobot_MAX98357A::scanSDMusic(String * musicList) 278 | { 279 | musicCount = 0; // Discard original list 280 | listDir(SD, "/"); 281 | uint8_t i = 0; 282 | while(i < musicCount){ 283 | musicList[i] = _musicList[i]; 284 | i++; 285 | } 286 | 287 | // Set playing music by default 288 | char SDName[80]="/sd"; 289 | strcat(SDName, musicList[0].c_str()); 290 | strcpy(fileName, SDName); 291 | } 292 | 293 | void DFRobot_MAX98357A::playSDMusic(const char *musicName) 294 | { 295 | SDPlayerControl(SD_AMPLIFIER_STOP); 296 | char SDName[80]="/sd"; // The default SD card mount point in SD.h 297 | strcat(SDName, musicName); // It need to be an absolute path. 298 | strcpy(fileName, SDName); 299 | SDPlayerControl(SD_AMPLIFIER_PLAY); 300 | } 301 | 302 | void DFRobot_MAX98357A::SDPlayerControl(uint8_t CMD) 303 | { 304 | SDAmplifierMark = CMD; 305 | delay(10); // Wait music playback to stop 306 | } 307 | 308 | String DFRobot_MAX98357A::getMetadata(uint8_t type) 309 | { 310 | _metadata = ""; 311 | if(_avrcConnected){ 312 | esp_avrc_ct_send_metadata_cmd(type, type); // Request metadata from remote Bluetooth device via AVRC command 313 | for(uint8_t i=0; i<20; i++){ // Wait response 314 | if(0 != _metaFlag){ 315 | break; 316 | } 317 | delay(100); 318 | } 319 | _metaFlag = 0; 320 | } 321 | return _metadata; 322 | } 323 | 324 | uint8_t * DFRobot_MAX98357A::getRemoteAddress(void) 325 | { 326 | if(!_avrcConnected){ // Bluetooth AVRC is not connected 327 | return NULL; 328 | } 329 | return remoteAddress; 330 | } 331 | 332 | void DFRobot_MAX98357A::setVolume(float vol) 333 | { 334 | vol /= 5.0; // vol range is 0-9 335 | _volume = constrain(vol, 0.0, 2.0); 336 | } 337 | 338 | void DFRobot_MAX98357A::openFilter(int type, float fc) 339 | { 340 | if(bq_type_lowpass == type){ // Set low-pass filter 341 | setFilter(_filterLLP, type, fc); 342 | setFilter(_filterRLP, type, fc); 343 | }else{ // Set high-pass filter 344 | setFilter(_filterLHP, type, fc); 345 | setFilter(_filterRHP, type, fc); 346 | } 347 | _filterFlag = true; 348 | } 349 | 350 | void DFRobot_MAX98357A::closeFilter(void) 351 | { 352 | _filterFlag = false; 353 | } 354 | 355 | void DFRobot_MAX98357A::setFilter(Biquad * _filter, int _type, float _fc) 356 | { 357 | _fc = (constrain(_fc, 2.0, 20000.0)) / (float)_sampleRate; // Ratio of filter threshold to sampling frequency, range: 0.0-0.5 358 | float Q; 359 | for(int i; imeta_rsp.attr_length + 1); 435 | memcpy(attr_text, rc->meta_rsp.attr_text, rc->meta_rsp.attr_length); 436 | attr_text[rc->meta_rsp.attr_length] = 0; 437 | _metadata = String(attr_text); 438 | _metaFlag = rc->meta_rsp.attr_id; 439 | DBG("_metadata"); 440 | DBG(_metadata); 441 | DBG(rc->meta_rsp.attr_id); 442 | 443 | free(attr_text); 444 | break; 445 | } 446 | /*!< connection state changed event */ 447 | case ESP_AVRC_CT_CONNECTION_STATE_EVT:{ 448 | /*!< connection established */ 449 | _avrcConnected = rc->conn_stat.connected; 450 | if(_avrcConnected){ 451 | uint8_t * p = rc->conn_stat.remote_bda; 452 | for(uint8_t i=0; i<6; i++){ 453 | remoteAddress[i] = *(p + i); 454 | DBG(remoteAddress[i], HEX); 455 | } 456 | /*!< disconnecting remote device */ 457 | }else{ 458 | DBG(sizeof(remoteAddress)); 459 | memset(remoteAddress, 0, 6); 460 | } 461 | break; 462 | } 463 | /*!< passthrough response event */ 464 | case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT: 465 | /*!< notification event */ 466 | case ESP_AVRC_CT_CHANGE_NOTIFY_EVT: 467 | /*!< feature of remote device indication event */ 468 | case ESP_AVRC_CT_REMOTE_FEATURES_EVT: 469 | /*!< supported notification events capability of peer device */ 470 | case ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT: 471 | /*!< play status response event */ 472 | case ESP_AVRC_CT_PLAY_STATUS_RSP_EVT: 473 | /*!< set absolute volume response event */ 474 | case ESP_AVRC_CT_SET_ABSOLUTE_VOLUME_RSP_EVT: 475 | break; 476 | default: 477 | // "unhandled AVRC event: %d", event 478 | break; 479 | } 480 | } 481 | 482 | void DFRobot_MAX98357A::audioDataProcessCallback(const uint8_t *data, uint32_t len) 483 | { 484 | int16_t* data16 = (int16_t*)data; // Convert to 16-bit sample data 485 | int16_t processedData[2]; // Store the processed audio data 486 | int count = len / 4; // The number of audio data to be processed in int16_t[2] 487 | size_t i2s_bytes_write = 0; // i2s_write() the variable storing the number of data to be written 488 | 489 | if(!_filterFlag){ // Change sample data only according to volume multiplier 490 | for(int i=0; ifp = fopen(fileName, "rb"); 529 | if(wav->fp == NULL){ 530 | DBG("Unable to open wav file."); 531 | DBG(fileName); 532 | SDAmplifierMark = SD_AMPLIFIER_STOP; 533 | continue; 534 | } 535 | if(fread(&(wav->header.riffType), 1, 4, wav->fp) != 4){ 536 | DBG("couldn't read RIFF_ID."); 537 | SDAmplifierMark = SD_AMPLIFIER_STOP; 538 | continue; /* bad error "couldn't read RIFF_ID" */ 539 | } 540 | if(strncmp("RIFF", wav->header.riffType, 4)){ 541 | DBG("RIFF descriptor not found.") ; 542 | SDAmplifierMark = SD_AMPLIFIER_STOP; 543 | continue; 544 | } 545 | fread(&(wav->header.riffSize), 4, 1, wav->fp); 546 | if(fread(&wav->header.waveType, 1, 4, wav->fp) != 4){ 547 | DBG("couldn't read format"); 548 | SDAmplifierMark = SD_AMPLIFIER_STOP; 549 | continue; /* bad error "couldn't read format" */ 550 | } 551 | if(strncmp("WAVE", wav->header.waveType, 4)){ 552 | DBG("WAVE chunk ID not found.") ; 553 | SDAmplifierMark = SD_AMPLIFIER_STOP; 554 | continue; 555 | } 556 | if(fread(&(wav->header.formatType), 1, 4, wav->fp) != 4){ 557 | DBG("couldn't read format_ID"); 558 | SDAmplifierMark = SD_AMPLIFIER_STOP; 559 | continue; /* bad error "couldn't read format_ID" */ 560 | } 561 | if(strncmp("fmt", wav->header.formatType, 3)){ 562 | DBG("fmt chunk format not found."); 563 | SDAmplifierMark = SD_AMPLIFIER_STOP; 564 | continue; 565 | } 566 | fread(&(wav->header.formatSize), 4, 1, wav->fp); 567 | fread(&(wav->header.compressionCode), 2, 1, wav->fp); 568 | fread(&(wav->header.numChannels), 2, 1, wav->fp); 569 | fread(&(wav->header.sampleRate), 4, 1, wav->fp); 570 | fread(&(wav->header.bytesPerSecond), 4, 1, wav->fp); 571 | fread(&(wav->header.blockAlign), 2, 1, wav->fp); 572 | fread(&(wav->header.bitsPerSample), 2, 1, wav->fp); 573 | while(1){ 574 | if(fread(&wav->header.dataType1, 1, 1, wav->fp) != 1){ 575 | DBG("Unable to read data chunk ID."); 576 | free(wav); 577 | break; 578 | } 579 | if(strncmp("d", wav->header.dataType1, 1) == 0){ 580 | fread(&wav->header.dataType2, 3, 1, wav->fp); 581 | if(strncmp("ata", wav->header.dataType2, 3) == 0){ 582 | fread(&(wav->header.dataSize),4,1,wav->fp); 583 | break; 584 | } 585 | } 586 | } 587 | 588 | i2s_set_sample_rates(I2S_NUM_0, wav->header.sampleRate); // Set I2S sampling rate based on the parsed audio sampling frequency 589 | 590 | while(fread(&wav->header.data, 1 , 800 , wav->fp)){ 591 | audioDataProcessCallback((uint8_t *)&wav->header.data, 800); // Send the parsed audio data to the amplifier broadcast function 592 | if(SD_AMPLIFIER_STOP == SDAmplifierMark){ 593 | break; 594 | } 595 | while(SD_AMPLIFIER_PAUSE == SDAmplifierMark){ 596 | vTaskDelay(100); 597 | } 598 | } 599 | 600 | fclose(wav->fp); 601 | free(wav); 602 | SDAmplifierMark = SD_AMPLIFIER_STOP; 603 | vTaskDelay(100); 604 | } 605 | vTaskDelete(xPlayWAV); 606 | } 607 | -------------------------------------------------------------------------------- /src/DFRobot_MAX98357A.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * @file DFRobot_MAX98357A.h 3 | * @brief Define infrastructure of DFRobot_MAX98357A class 4 | * @details Configure a classic Bluetooth, pair with Bluetooth devices, receive Bluetooth audio, 5 | * @n Process simple audio signal, and pass it into the amplifier using I2S communication 6 | * @copyright Copyright (c) 2010 DFRobot Co.Ltd (http://www.dfrobot.com) 7 | * @license The MIT License (MIT) 8 | * @author [qsjhyy](yihuan.huang@dfrobot.com) 9 | * @version V1.0 10 | * @date 2022-01-20 11 | * @url https://github.com/DFRobot/DFRobot_MAX98357A 12 | */ 13 | #ifndef __DFRobot_AMPLIFIER_H__ 14 | #define __DFRobot_AMPLIFIER_H__ 15 | 16 | #include 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include "esp_avrc_api.h" 23 | 24 | #include 25 | 26 | #include "Biquad.h" // Code from https://www.earlevel.com/main/2012/11/26/biquad-c-source-code/ . Thank you very much! 27 | 28 | #include "SD.h" 29 | 30 | // #define ENABLE_DBG //!< Open this macro and you can see the details of the program 31 | #ifdef ENABLE_DBG 32 | #define DBG(...) {Serial.print("[");Serial.print(__FUNCTION__); Serial.print("(): "); Serial.print(__LINE__); Serial.print(" ] "); Serial.println(__VA_ARGS__);} 33 | #else 34 | #define DBG(...) 35 | #endif 36 | 37 | #define NUMBER_OF_FILTER ((int)(3)) //!< The number of the cascaded filter 38 | 39 | #define SD_AMPLIFIER_PLAY ((uint8_t)1) //!< Playback control of audio in SD card - start playback 40 | #define SD_AMPLIFIER_PAUSE ((uint8_t)2) //!< Playback control of audio in SD card - pause playback 41 | #define SD_AMPLIFIER_STOP ((uint8_t)3) //!< Playback control of audio in SD card - stop playback 42 | 43 | #define MAX98357A_VOICE_FROM_SD ((uint8_t)0) 44 | #define MAX98357A_VOICE_FROM_BT ((uint8_t)1) 45 | 46 | class DFRobot_MAX98357A 47 | { 48 | public: 49 | static uint8_t remoteAddress[6]; // Store the address of the remote Bluetooth device 50 | 51 | public: 52 | 53 | /************************ Init ********************************/ 54 | 55 | /** 56 | * @fn DFRobot_MAX98357A 57 | * @brief Constructor 58 | * @return None 59 | */ 60 | DFRobot_MAX98357A(void); 61 | 62 | /** 63 | * @fn begin 64 | * @brief Init function 65 | * @param btName - Name of the created Bluetooth device 66 | * @param bclk - I2S communication pin number, serial clock (SCK), aka bit clock (BCK) 67 | * @param lrclk - I2S communication pin number, word select (WS), i.e. command (channel) select, used to switch between left and right channel data 68 | * @param din - I2S communication pin number, serial data signal (SD), used to transmit audio data in two's complement format 69 | * @return true on success, false on error 70 | */ 71 | bool begin(const char *btName="bluetoothAmplifier", 72 | int bclk=25, 73 | int lrclk=26, 74 | int din=27); 75 | 76 | /** 77 | * @fn initI2S 78 | * @brief Initialize I2S 79 | * @param _bclk - I2S communication pin number, serial clock (SCK), aka bit clock (BCK) 80 | * @param _lrclk - I2S communication pin number, word select (WS), i.e. command (soundtrack) select, used to switch the data of the left and right channels 81 | * @param _din - I2S communication pin number, serial data signal (SD), used to transmit audio data in two's complement format 82 | * @return true on success, false on error 83 | */ 84 | bool initI2S(int _bclk, int _lrclk, int _din); 85 | 86 | /** 87 | * @fn initBluetooth 88 | * @brief Initialize bluetooth 89 | * @param _btName - Name of the created Bluetooth device 90 | * @return true on success, false on error 91 | */ 92 | bool initBluetooth(const char * _btName); 93 | 94 | /** 95 | * @fn initSDCard 96 | * @brief Initialize SD card 97 | * @param csPin cs pin number for spi communication of SD card module 98 | * @return true on success, false on error 99 | */ 100 | bool initSDCard(uint8_t csPin=5); 101 | 102 | /*************************** Function ******************************/ 103 | 104 | /** 105 | * @fn scanSDMusic 106 | * @brief Scan the music files in WAV format in the SD card 107 | * @param musicList - The music files in WAV format scanned from the SD card. Type is character string array 108 | * @return None 109 | * @note Only support English for path name of music files and WAV for their format currently 110 | */ 111 | void scanSDMusic(String * musicList); 112 | 113 | /** 114 | * @fn playSDMusic 115 | * @brief Play music files in the SD card 116 | * @param Filename - music file name, only support the music files in .wav format currently 117 | * @note Music file name must be an absolute path like /musicDir/music.wav 118 | * @return None 119 | * @note Only support English for path name of music files and WAV for their format currently 120 | */ 121 | void playSDMusic(const char *Filename); 122 | 123 | /** 124 | * @fn SDPlayerControl 125 | * @brief SD card music playback control interface 126 | * @param CMD - Playback control command: 127 | * @n SD_AMPLIFIER_PLAY: Start to play music, which can be played from the position where you paused before 128 | * @n If no music file is selected through playSDMusic(), the first one in the list will be played by default. 129 | * @n Playback error may occur if music files are not scanned from SD card in the correct format (only support English for path name of music files and WAV for their format currently) 130 | * @n SD_AMPLIFIER_PAUSE: Pause playback, keep the playback position of the current music file 131 | * @n SD_AMPLIFIER_STOP: Stop playback, stop the current music playback 132 | * @return None 133 | */ 134 | void SDPlayerControl(uint8_t CMD); 135 | 136 | /** 137 | * @fn getMetadata 138 | * @brief Get "metadata" through AVRC command 139 | * @param type - The type of metadata to be obtained, and the parameters currently supported: 140 | * @n ESP_AVRC_MD_ATTR_TITLE ESP_AVRC_MD_ATTR_ARTIST ESP_AVRC_MD_ATTR_ALBUM 141 | * @return The corresponding type of "metadata" 142 | */ 143 | String getMetadata(uint8_t type); 144 | 145 | /** 146 | * @fn getRemoteAddress 147 | * @brief Get the address of the remote Bluetooth device 148 | * @note The address will be obtained after the module is paired with the remote Bluetooth device and successfully communicates with it based on the Bluetooth AVRCP protocol. 149 | * @return Return the array pointer storing the address of the remote Bluetooth device 150 | * @n Return None when the module does not connect to the remote device or failed to communicate with it based on the Bluetooth AVRCP protocol. 151 | * @n AVRCP(Audio Video Remote Control Profile) 152 | */ 153 | uint8_t * getRemoteAddress(void); 154 | 155 | /** 156 | * @fn setVolume 157 | * @brief Set volume 158 | * @param vol - Set volume, the range can be set to 0-9 159 | * @note 5 for the original volume of audio data, no increase or decrease 160 | * @return None 161 | */ 162 | void setVolume(float vol); 163 | 164 | /** 165 | * @fn openFilter 166 | * @brief Open audio filter 167 | * @param type - bq_type_highpass: open high-pass filtering; bq_type_lowpass: open low-pass filtering 168 | * @param fc - Threshold of filtering, range: 2-20000 169 | * @note For example, setting high-pass filter mode and the threshold of 500 indicates to filter out the audio signal below 500; high-pass filter and low-pass filter will work simultaneously. 170 | * @return None 171 | */ 172 | void openFilter(int type, float fc); 173 | 174 | /** 175 | * @fn closeFilter 176 | * @brief Close the audio filter 177 | * @return None 178 | */ 179 | void closeFilter(void); 180 | 181 | /** 182 | * @fn reverseLeftRightChannels 183 | * @brief Reverse left and right channels, When you find that the left 184 | * @n and right channels play opposite, you can call this interface to adjust 185 | * @return None 186 | */ 187 | void reverseLeftRightChannels(void); 188 | 189 | protected: 190 | 191 | /** 192 | * @fn end 193 | * @brief End communication, release resources 194 | * @return None 195 | */ 196 | void end(void); 197 | 198 | /** 199 | * @fn listDir 200 | * @brief Traversal of SD card files 201 | * @param fs - SD card file stream pointer 202 | * @param dirName - Start traversal of SD card catalogue 203 | * @return None 204 | */ 205 | void listDir(fs::FS &fs, const char * dirName); 206 | 207 | /** 208 | * @fn setFilter 209 | * @brief Set filter 210 | * @param _filter - The filter to be set 211 | * @param _type - bq_type_highpass: open high-pass filtering; bq_type_lowpass: open low-pass filtering 212 | * @param _fc - Threshold of filtering, range: 2-20000 213 | * @return None 214 | */ 215 | void setFilter(Biquad * _filter, int _type, float _fc); 216 | 217 | /** 218 | * @fn filterToWork 219 | * @brief Make the filter work, process audio data 220 | * @param filterHP - The high-pass filter to be used 221 | * @param filterLP - The low-pass filter to be used 222 | * @param rawData - The raw audio data to be processed. float 223 | * @return The processed audio data int16_t 224 | */ 225 | static int16_t filterToWork(Biquad * filterHP, Biquad * filterLP, float rawData); 226 | 227 | /*************************** Function ******************************/ 228 | 229 | /** 230 | * @fn audioDataProcessCallback 231 | * @brief esp_a2d_sink_register_data_callback() function, 232 | * @n Process the audio stream data of Bluetooth A2DP protocol communication 233 | * @param data - The audio data from the remote Bluetooth device 234 | * @param len - Byte length of audio data 235 | * @return None 236 | * @note Because of some factors like action scope, the function should be static. Therefore it is shared by multiple objects of the class. 237 | */ 238 | static void audioDataProcessCallback(const uint8_t *data, uint32_t len); 239 | 240 | /** 241 | * @fn filterToWork 242 | * @brief esp_a2d_register_callback() function, used to process the event of Bluetooth A2DP protocol communication 243 | * @param event - Type of the triggered A2DP event 244 | * @param param - The parameter information corresponding to the event 245 | * @return None 246 | * @note Because of some factors like action scope, the function should be static. Therefore it is shared by multiple objects of the class. 247 | */ 248 | static void a2dpCallback(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param); 249 | 250 | /** 251 | * @fn avrcCallback 252 | * @brief esp_avrc_ct_register_callback() function, used to process the event of Bluetooth AVRC protocol communication 253 | * @param event - Type of the triggered AVRC event 254 | * @param param - The parameter information corresponding to the event 255 | * @return None 256 | * @note Because of some factors like action scope, the function should be static. Therefore it is shared by multiple objects of the class. 257 | */ 258 | static void avrcCallback(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t *param); 259 | 260 | /** 261 | * @fn playWAV 262 | * @brief The parsing play function for audio files in WAV format 263 | * @param arg - Corresponding parameter information 264 | * @return None 265 | * @note Because of some factors like action scope, the function should be static. Therefore it is shared by multiple objects of the class. 266 | */ 267 | static void playWAV(void *arg); 268 | 269 | private: 270 | 271 | }; 272 | 273 | #endif 274 | --------------------------------------------------------------------------------