esp32 播放多个音频源的音频,例如播放本地flash的mp3,http或者蓝牙传来的音频流等。
本次例程介绍更加简单的播放器初始化,并在播放器中添加http_stream
和flash_tone_stream
等输入流来实现 百度语音合成
与播放本地音频
的效果。
esp-idf提供了一套更加便捷的创建pipeline管道的api函数,接口在esp_audio.h中定义。使用这套api能更快的初始化一个音频处理管道。
一个音频管道就是三四个部分组成,输入流->编解码->适配->输出流,而esp_audio.h中提供了几个重要的函数来实现以上:其中必须要有的element就是输入,编解码,输出。
几个比较重要的函数:
1,添加音频输入源,这里添加了http和flash_tone
esp_audio_input_stream_add(player, http_stream_reader);
2,添加音频输出源,这里添加了i2s流,输出到ac101播放音频
esp_audio_output_stream_add(player, i2s_stream_writer);
3,添加音频编解码仓库
esp_audio_codec_lib_add(player, AUDIO_CODEC_TYPE_DECODER, mp3_decoder_init(&mp3_dec_cfg));
具体使用的流程:
/*板子音频芯片初始化 音频引脚初始化 更换芯片或者板子时要注意*/
esp_audio_cfg_t cfg = DEFAULT_ESP_AUDIO_CONFIG();
ESP_LOGI(TAG, "audio_board_init");
//音频硬件初始化
audio_board_handle_t board_handle = audio_board_init();
cfg.vol_handle = board_handle->audio_hal;
cfg.prefer_type = ESP_AUDIO_PREFER_MEM;
ESP_LOGI(TAG, "create player");
//!对于我们的开发板,要使用48k才能正常说话(喇叭)
cfg.resample_rate = 48000;
player = esp_audio_create(&cfg);
//音频编解码芯片ac101初始化
ESP_LOGI(TAG, "init codec");
audio_hal_ctrl_codec(board_handle->audio_hal, AUDIO_HAL_CODEC_MODE_BOTH, AUDIO_HAL_CTRL_START);
//创建http流
ESP_LOGI(TAG, "[2.1] Create http stream to read data");
http_stream_cfg_t http_cfg = HTTP_STREAM_CFG_DEFAULT();
//设置http回调函数
http_cfg.event_handle = _http_stream_event_handle;
http_cfg.type = AUDIO_STREAM_READER;
audio_element_handle_t http_stream_reader = http_stream_init(&http_cfg);
esp_audio_input_stream_add(player, http_stream_reader);
//创建flash_tone流
ESP_LOGI(TAG, "create flash tone stream reader");
tone_stream_cfg_t tone_cfg = TONE_STREAM_CFG_DEFAULT();
tone_cfg.type = AUDIO_STREAM_READER;
ESP_LOGI(TAG, "add stream reader to player");
esp_audio_input_stream_add(player, tone_stream_init(&tone_cfg));
//mp3解码
ESP_LOGI(TAG, "create mp3 decoder and add to player");
mp3_decoder_cfg_t mp3_dec_cfg = DEFAULT_MP3_DECODER_CONFIG();
esp_audio_codec_lib_add(player, AUDIO_CODEC_TYPE_DECODER, mp3_decoder_init(&mp3_dec_cfg));
//创建i2s writer并添加到player的输出源
ESP_LOGI(TAG, "create i2s writer and add to player");
i2s_stream_cfg_t i2s_writer = I2S_STREAM_CFG_DEFAULT();
//!注意要和player的采样率48000相同
i2s_writer.i2s_config.sample_rate = 48000;
//右声道
i2s_writer.i2s_config.channel_format = I2S_CHANNEL_FMT_ALL_RIGHT;
i2s_writer.type = AUDIO_STREAM_WRITER;
audio_element_handle_t i2s_stream_writer = i2s_stream_init(&i2s_writer);
esp_audio_output_stream_add(player, i2s_stream_writer);
ESP_LOGI(TAG, "http_stream_reader->mp3decode->player->i2s->ac101");
ESP_LOGI(TAG, "flash_stream_reader->mp3decode->player->i2s->ac101");
esp_audio_vol_set(player, 80);
之前介绍过esp32 播放本地flash中的mp3,没看过的朋友可以参考一下:ESP32 ADF 离线播放mp3 mp3烧录flash;
语音合成是将中文合成音频,然后播放出来。
本例程使用的是百度AI开放平台的语音合成API,具体的接口文档参考百度AI开放平台的详细介绍。百度语音合成文档
使用百度的API需要先获得token
,然后发送GET或者POST请求,请求中写入要合成的中文字符串,获取http音频流,将http音频流添加到以上的player中播放。
ADF为我们提供了获取百度token的代码,在ADF目录下components/adf_utils/cloud_services/baidu_access_token.c
中,使用baidu_get_access_token()
可以获取百度token。
调用以下函数实现语音播放,该函数会调用_http_stream_event_handle()回调函数,该函数负责处理这次http连接的所有操作。包括请求前的参数准备,请求开始,接收数据,断开连接等。如果成功返回http音频流,则会在player中播放
esp_audio_sync_play(player,"http://tsn.baidu.com/text2audio", 0);
在这个回调函数中我们主要处理的是http请求前的参数设置。包括token的获取,设置POST的body参数。其中text是一个全局变量,存放即将转成语音的字符串。
/*
* http stream回调函数
* 调用esp_audio_sync_play(player,BAIDU_TTS_URL, 0)会调用此回调函数
*/
int _http_stream_event_handle(http_stream_event_msg_t *msg)
{
esp_http_client_handle_t http_client = (esp_http_client_handle_t)msg->http_client;
switch(msg->event_id)
{
case HTTP_STREAM_PRE_REQUEST:
//准备请求数据 检查token
ESP_LOGI(TAG, "HTTP_STREAM_PRE_REQUEST");
if (baidu_access_token == NULL) {
// Must freed `baidu_access_token` after used 获得token
ESP_LOGI(TAG, "try to get token");
baidu_access_token = baidu_get_access_token(BAIDU_ACCESS_KEY, BAIDU_SECRET_KEY);
}
if (baidu_access_token == NULL) {
ESP_LOGE(TAG, "Error issuing access token");
return ESP_FAIL;
}
/* 组装body参数:lan:语言;cuid:设备id;ctp:...;aue:文件格式;spd:语速;pit:音调;vol:音量;per:人声*/
int data_len = snprintf(request_data, 1024, "lan=zh&cuid=ppp&ctp=2&aue=3&spd=4&pit=5&vol=5&per=0&tok=%s&tex=%s", baidu_access_token, text);
//将token装填进http请求体
esp_http_client_set_post_field(http_client, request_data, data_len);
//post
esp_http_client_set_method(http_client, 1);
break;
case HTTP_STREAM_ON_REQUEST:
ESP_LOGI(TAG, "HTTP_STREAM_ON_REQUEST");
break;
case HTTP_STREAM_ON_RESPONSE:
ESP_LOGI(TAG, "HTTP_STREAM_ON_RESPONSE");
break;
case HTTP_STREAM_POST_REQUEST:
ESP_LOGI(TAG, "HTTP_STREAM_POST_REQUEST");
break;
case HTTP_STREAM_FINISH_REQUEST:
ESP_LOGI(TAG, "HTTP_STREAM_FINISH_REQUEST");
break;
case HTTP_STREAM_RESOLVE_ALL_TRACKS:
ESP_LOGI(TAG, "HTTP_STREAM_RESOLVE_ALL_TRACKS");
break;
case HTTP_STREAM_FINISH_TRACK:
ESP_LOGI(TAG, "HTTP_STREAM_FINISH_TRACK");
break;
case HTTP_STREAM_FINISH_PLAYLIST:
ESP_LOGI(TAG, "HTTP_STREAM_FINISH_PLAYLIST");
break;
}
return ESP_OK;
}
http stream的播放并不复杂,主要的工作就是设置好http请求头,获取http流。
但不得不夸一下百度,他的语音合成速度非常快,实用性非常高