2.4. DEMO_AUDIO工程说明
概述
本工程展示了:
1.本地播歌接口使用方法,播放SD卡、FLASH和U盘的音频文件。
2.网络播歌接口使用方法,在同一个局域网内,设备可通过手机DLNA进行投歌播放。
3.录音模块接口使用方法,将MIC、LINEIN、PLNK、IIS的数字信号采集后推到DAC播放,也可保存录音文件到SD卡或者通过DAC模拟直通。
4.打断唤醒使用方法,通过离线唤醒词进行唤醒
2.4.1. 工程配置说明
1.在
apps/demo/demo_audio/include/app_config.h
选择需要使用的模式功能,程序运行后通过按键进行切换模式(可通过提示音进行确认模式)
#define CONFIG_LOCAL_MUSIC_MODE_ENABLE //mode:本地播放模式使能
#define CONFIG_RECORDER_MODE_ENABLE //mode:录音模式使能
#ifdef CONFIG_NET_ENABLE
#define CONFIG_NET_MUSIC_MODE_ENABLE //mode:网络播放模式使能
#define CONFIG_ASR_ALGORITHM_ENABLE //mode:打断唤醒模式使能
#endif
- 2.对应板级文件board.c:
详情查看AUDIO_ADC、AUDIO_DAC、IIS、PDM LINK等文档配置,配置dac_data、adc_data、iis0_data和plnk0_data
//DAC配置(需要对应原理图(原理图请移步gitee)看下DAC支不支持,如AC7911BA只引出DACL,AC7916A引出DACL+DACR) //1. 单通道 单端 fDACL(左声道) static const struct dac_platform_data dac_data = { .pa_auto_mute = 1, //关闭DAC不输出声音时是否自动MUTE功放 0:不mute功放 1:MUTE功放 .pa_mute_port = 0xff, //功放MUTE IO ,只在pa_auto_mute为1时有效 .pa_mute_value = 1, //MUTE电平值,只在pa_auto_mute为1时有效 0:低电平 1:高电平 .differ_output = 0, //是否使用差分输出模式 0:不使用 1:使用 .hw_channel = 0x01, //硬件DAC模拟通道 .ch_num = 1, //差分只需开一个通道 .vcm_init_delay_ms = 1000, #ifdef CONFIG_DEC_ANALOG_VOLUME_ENABLE .fade_enable = 1, //模拟音量淡入淡出使能位 .fade_delay_ms = 40, //模拟音量淡入淡出延时 #endif }; #if 0 //例子,其他不相关/不修改参数不列出来 //2. 单通道 单端 fDACR(右声道) static const struct dac_platform_data dac_data = { .differ_output = 0, //选择是否采用差分输出模式 .hw_channel = 0x02, //BIT(0)使用DACL | BIT(1)使用DACR .ch_num = 1, }; //3. 双通道 立体声 fDACL+fDACR(左/右声道) static const struct dac_platform_data dac_data = { .differ_output = 0, //选择是否采用差分输出模式 .hw_channel = 0x03, //BIT(0)使用DACL | BIT(1)使用DACR .ch_num = 2, }; //4. 双通道 差分 fDACL/fDACR(左右声道合并) static const struct dac_platform_data dac_data = { .differ_output = 1, //选择是否采用差分输出模式 .hw_channel = 0x03, //BIT(0)使用DACL | BIT(1)使用DACR .ch_num = 1, //差分只需开一个通道 }; //5. 四通道 差分 fDACL/fDACR+rDACL/rDACR(前后左声道合并,前后右声道合并,相当于抗干扰好一点的左/右声道) static const struct dac_platform_data dac_data = { .differ_output = 1, //选择是否采用差分输出模式 .hw_channel = 0x0f, //BIT(0)使用fDACL | BIT(1)使用fDACR | BIT(2)使用rDACL | BIT(3)使用rDACR .ch_num = 2, //差分只需开一个通道 }; //6. 四通道 立体声 fDACL+fDACR+rDACL+rDACR(立体环绕声) static const struct dac_platform_data dac_data = { .differ_output = 0, //选择是否采用差分输出模式 .hw_channel = 0x0f, //BIT(0)使用fDACL | BIT(1)使用fDACR | BIT(2)使用rDACL | BIT(3)使用rDACR .ch_num = 4, }; #endif //MIC/LINEIN配置(需要对应原理图(原理图请移步gitee)看下支不支持,如AC7911BA引出MIC0P/MIC0N MIC1N MIC2P/MIC2N MIC3P ,AUX0 AUX1 AUX2 AUX3) static const struct adc_platform_data adc_data = { .mic_channel = LADC_CH_MIC0_P_N, // | LADC_CH_MIC2_P_N, //P代表硬件MIC直接正端,N代表硬件MIC只接负端,P_N代表硬件MIC采用差分接法 .linein_channel = NULL, //LADC_CH_AUX0 | LADC_CH_AUX2, .mic_ch_num = 1, //整个代码运行过程中会用到多少路通道AUDIO MIC .linein_ch_num = 0, //整个代码运行过程中会用到多少路通道AUDIO LINEIN .all_channel_open = 1, //四路全开,默认打开 .isel = 2, //电流档位选择,勿改动 .dump_num = 480, //丢弃刚打开硬件时的数据点数 }; //IIS配置(移步文档《IIS(ALNK)》) static const struct iis_platform_data iis0_data = { .channel_in = 0, //输入通道 BIT(X) .channel_out = BIT(0), //输出通道 BIT(X) .port_sel = IIS_PORTC, //IO组选择 .data_width = 0, //BIT(X)为通道X使用24bit模式,不配置的通道则使用16bit .mclk_output = 0, //1:输出mclk 0:不输出mclk .slave_mode = 0, //1:从机模式 0:主机模式 .dump_points_num = 320, //丢弃刚打开硬件时的数据点数 }; //PLNK数字麦配置 static void plnk0_port_remap_cb(void)//重映射函数 { //重映射PDM DAT-PA2 PDM CLK-PA3 重映射的DAT脚和CLK时钟脚可根据原理图所有引脚选择 extern int gpio_plnk_rx_input(u32 gpio, u8 index, u8 data_sel); gpio_plnk_rx_input(IO_PORTA_02, 0, 0);//plnk数字DAT0输入 ,当第三个参数为1时gpio_plnk_rx_input(IO_PORTA_02, 0, 0);plnk数字DAT1输入 gpio_output_channle(IO_PORTA_01, CH0_PLNK0_SCLK_OUT); //SCLK0使用outputchannel0 JL_IOMAP->CON3 |= BIT(18);//PLNK0 SCLK0 输出使能,若使用PLNK1数字麦1,JL_IOMAP->CON3 |= BIT(19) } static void plnk0_port_unremap_cb(void)//解除重映射 { JL_IOMAP->CON3 &= ~BIT(18); //PLNK0 SCLK0 输出失能 若使用PLNK1数字麦1,JL_IOMAP->CON3 &= ~BIT(19); gpio_clear_output_channle(IO_PORTA_01, CH0_PLNK0_SCLK_OUT);//释放outputchannel0 gpio_set_die(IO_PORTA_02, 0);//数字/模拟输入切换 } //plnk的时钟和数据引脚都采用重映射的使用例子 static const struct plnk_platform_data plnk0_data = { .hw_channel = PLNK_CH_MIC_L, //使用DAT0输入 .clk_out = 1, // 使能时钟输出 .port_remap_cb = plnk0_port_remap_cb, //重映射 .port_unremap_cb = plnk0_port_unremap_cb, //解除重映射 .sample_edge = 0, //0:下降沿采样数据 1:上升沿采样数据 .share_data_io = 1, //双数字麦是否共用同一个数据输入IO .high_gain = 1, //0:-6db增益 1:0db增益 .dc_cancelling_filter = 14, //去直流滤波器等级0-15 .dump_points_num = 640, //丢弃刚打开硬件时的数据点数 }; static const struct audio_pf_data audio_pf_d = { .adc_pf_data = &adc_data, .dac_pf_data = &dac_data, .iis0_pf_data = &iis0_data, .plnk0_pf_data = &plnk0_data, }; static const struct audio_platform_data audio_data = { .private_data = (void *) &audio_pf_d, };
定义宏#define CONFIG_AUDIO_ENC_SAMPLE_SOURCE //录音设备源选择
#define AUDIO_ENC_SAMPLE_SOURCE_MIC 0 //录音输入源: 模拟MIC #define AUDIO_ENC_SAMPLE_SOURCE_PLNK0 1 //录音输入源:数字麦PLNK0 #define AUDIO_ENC_SAMPLE_SOURCE_PLNK1 2 //录音输入源:数字麦PLNK1 #define AUDIO_ENC_SAMPLE_SOURCE_IIS0 3 //录音输入源:IIS0 #define AUDIO_ENC_SAMPLE_SOURCE_IIS1 4 //录音输入源:IIS1 #define AUDIO_ENC_SAMPLE_SOURCE_LINEIN 5 //录音输入源:LINEIN #define CONFIG_AUDIO_ENC_SAMPLE_SOURCE AUDIO_ENC_SAMPLE_SOURCE_MIC //录音输入源选择
2.4.2. 工程操作说明
测试前请根据实际原理图配置好相应板级文件,尤其是串口打印和AUDIO ADC的对应IO口配置,还有AD按键对应阻值的配置
- 1.按键操功能:
切换模式:KEY_MODE按键。
- (1) 本地播歌模式:
A)暂停或继续播放:短按KEY_OK按键。
B)切换文件夹播放:长按KEY_OK按键。
C)音量增加:短按KEY_VOLUME_INC按键。
D)音量减小:短按KEY_VOLUME_DEC按键。
E)播放上一首:长按KEY_VOLUME_DEC按键。
F)播放下一首:长按KEY_VOLUME_INC按键。
- (2) 网络播放模式:
A)暂停或继续播放:短按KEY_OK按键。
B)音量增加:短按KEY_VOLUME_INC按键。
C)音量减小:短按KEY_VOLUME_DEC按键。
D)播放上一首:长按KEY_VOLUME_DEC按键。
E)播放下一首:长按KEY_VOLUME_INC按键。
- (3) 录音模式:
A)暂停或继续录音:短按KEY_OK按键。
B)音量增加:短按KEY_VOLUME_INC按键。
C)音量减小:短按KEY_VOLUME_DEC按键。
D)ADC模拟增益增加:长按KEY_VOLUME_INC按键。
E)ADC模拟增益减小:长按KEY_VOLUME_DEC按键。
F)模拟直通到DAC:长按KEY_OK按键。
G)录音到sd卡:长按KEY_MODE按键。
2.使用网络播放模式需要设备连上路由器,在app_config.h配置相应的路由器名称SSID和密码PWD,使用demo_DevKitBoard工程需在demo_config.h打开宏#define USE_DEMO_WIFI_TEST然后再wifi_demo_task.c中设置WIFI相应的SSID和PWD,然后打开手机里的各大音乐播放器app,点击DLNA投屏找到设备投屏。
3.使用本地播放模式需要插入SD卡并且卡里面有支持解码格式的歌曲文件。
4.使用录音模式linein输入时,使用音频线连接电脑音频输出,另一头连接AUX信号输入,连接原理图对应的IO。MIC/PLNK输入需要相应的元器件。
2.4.3. 代码流程
模式切换audio_demo_mode_switch():创建audio_app,通过app_state_machine接口使得存在多个模式,并且多个模式之间是互斥关系(非后台)的情况下, 建立多个app_state_machine切换可以使得系统资源合理利用和降低CPU的消耗。
static const struct audio_app_t audio_app_table[] = {//模式切换表
#ifdef CONFIG_LOCAL_MUSIC_MODE_ENABLE //本地播放模式
{"FlashMusic.mp3", "local_music"},
#endif
#ifdef CONFIG_NET_MUSIC_MODE_ENABLE //网络播放模式
{"NetMusic.mp3", "net_music" },
#endif
#ifdef CONFIG_RECORDER_MODE_ENABLE //录音模式
{"Recorder.mp3", "recorder"},
#endif
#ifdef CONFIG_ASR_ALGORITHM_ENABLE //打断唤醒模式
{"AiSpeaker.mp3", "ai_speaker"},
#endif
};
int audio_demo_mode_switch(void)//模式选择
{
if (mode_index >= ARRAY_SIZE(audio_app_table)) {
mode_index = 0;
}
struct intent it;
if (get_current_app()) {
init_intent(&it);
it.name = audio_app_table[mode_index].app_name;
it.action = ACTION_STOP; //退出当前模式
start_app(&it);
}
char path[64];
sprintf(path, "%s%s", CONFIG_VOICE_PROMPT_FILE_PATH, audio_app_table[mode_index].tone_file_name);
return app_play_tone_file(path);
}
2.4.4. 模式说明
1.本地播放模式 local_music.c
未挂载sd卡时播放flash内提示音资源,插sd卡播放sd中内容,详情sd挂载使用参考sd文档
if (storage_device_ready()) { return local_music_switch_local_device(CONFIG_MUSIC_PATH_SD); } else { return local_music_switch_local_device(CONFIG_MUSIC_PATH_FLASH); }
本地播放可选择FLASH、SD卡和U盘之间切换,通过扫描文件和搜索指定音频格式进行选择解码播放,有全盘搜索播放和目录搜索播放。
//释放资源,切换播放源设备
static int local_music_switch_local_device(const char *path)
{//SD卡、U盘切换
log_i("local_music_switch_local_device\n");
if (__this->dir_list) {//搜索文件夹
fscan_release(__this->dir_list);
__this->dir_list = NULL;
}
if (__this->fscan) {//扫描文件
fscan_release(__this->fscan);
__this->fscan = NULL;
}
if (__this->wait_sd) {//等待SD卡挂载
wait_completion_del(__this->wait_sd);
__this->wait_sd = 0;
}
if (__this->wait_udisk) {//等待U盘挂载
wait_completion_del(__this->wait_udisk);
__this->wait_udisk = 0;
}
local_music_dec_stop();//播放结束
if (path == NULL) {
return -1;
}
__this->local_path = path;//播放路径
local_music_dec_switch_dir(FSEL_FIRST_FILE);//本地播放选择文件夹
return 0;
}
//切换文件夹
static int local_music_dec_switch_dir(int fsel_mode)
{
......
......
//全盘播放
if (__this->local_play_all && __this->local_path != CONFIG_MUSIC_PATH_FLASH) {
//全盘搜索
if (__this->fscan) {
fscan_release(__this->fscan);
}
#if CONFIG_DEC_DECRYPT_ENABLE //扫描相应格式文件,包含子目录-r,-t扫描文件格式 ,-sn按照按照文件号排序,扫描目录层数2
__this->fscan = fscan(__this->local_path, "-r -tMP3WMAWAVM4AAMRAPEFLAAACSPXOPUDTSADPSMP -sn", 2);
#else
__this->fscan = fscan(__this->local_path, "-r -tMP3WMAWAVM4AAMRAPEFLAAACSPXOPUDTSADP -sn", 2);
#endif
......
return local_music_dec_switch_file(FSEL_FIRST_FILE);//切换歌曲
}
//搜索文件夹
if (!__this->dir_list) {
__this->dir_list = fscan(__this->local_path, "-d -sn", 2);//扫描根目录文件夹
.......
}
//选择文件夹
__again:
do {
dir = fselect(__this->dir_list, fsel_mode, 0);//选择文件夹
if (dir) {
i++;
break;
}
if (fsel_mode == FSEL_NEXT_FILE) {//选择模式下一个文件
fsel_mode = FSEL_FIRST_FILE; //最开始文件开始
} else if (fsel_mode == FSEL_PREV_FILE) { //选择模式上一个文件
fsel_mode = FSEL_LAST_FILE; //最后一个文件开始
} else {
log_w("fselect_dir_faild, create dir\n");
return -1;
}
} while (i++ < __this->dir_list->file_number);//通过文件号选择文件
........
.......
len = fget_name(dir, (u8 *)name, sizeof(name) - 1);//获取文件名长度
........
.......
}
fclose(dir);
fname_to_path(path, __this->local_path, name, len, 1, 0);
#if 0 //此处播放指定目录,用户填写的目录路径要注意中文编码问题,看不懂就直接用16进制把路径打印出来
const char *user_dir = ""; //指定目录
if (!file && strcmp(path, user_dir)) {
log_i("dir name : %s\n", path);
if (fsel_mode == FSEL_FIRST_FILE) {
fsel_mode = FSEL_NEXT_FILE;
} else if (fsel_mode == FSEL_LAST_FILE) {
fsel_mode = FSEL_PREV_FILE;
}
goto __again;
}
#endif
/*搜索文件夹下的音频文件,按序号排序*/
#if CONFIG_DEC_DECRYPT_ENABLE //扫描相应格式文件,包含子目录-r,-t扫描文件格式 ,-sn按照按照文件号排序,扫描目录层数2
__this->fscan = fscan(path, "-tMP3WMAWAVM4AAMRAPEFLAAACSPXOPUDTSADPSMP -sn", 2);
#else
__this->fscan = fscan(path, "-tMP3WMAWAVM4AAMRAPEFLAAACSPXOPUDTSADP -sn", 2);
#endif
if (!file) {
if (!__this->fscan || !__this->fscan->file_number) {
if (fsel_mode == FSEL_FIRST_FILE) {//选择模式下一个文件
fsel_mode = FSEL_NEXT_FILE;//最开始文件开始
} else if (fsel_mode == FSEL_LAST_FILE) {//选择模式上一个文件
fsel_mode = FSEL_PREV_FILE;//最后一个文件开始
}
goto __again;
}
local_music_dec_switch_file(FSEL_FIRST_FILE);//切换歌曲
}
return 0;
}
2.网络播歌 net_music.c
通过ai_server平台服务器使用DLNA功能,在同一个局域网内可通过手机app支持的DLNA功能将手机上的音频投放到设备上播放。
#define CONFIG_DLNA_SDK_ENABLE //打开DLNA音乐播放功能
3.录音模式 recorder.c
//选择录音输入源
#define AUDIO_ENC_SAMPLE_SOURCE_MIC 0 //录音输入源: 模拟MIC
#define AUDIO_ENC_SAMPLE_SOURCE_PLNK0 1 //录音输入源:数字麦PLNK0
#define AUDIO_ENC_SAMPLE_SOURCE_PLNK1 2 //录音输入源:数字麦PLNK1
#define AUDIO_ENC_SAMPLE_SOURCE_IIS0 3 //录音输入源:IIS0
#define AUDIO_ENC_SAMPLE_SOURCE_IIS1 4 //录音输入源:IIS1
#define AUDIO_ENC_SAMPLE_SOURCE_LINEIN 5 //录音输入源:LINEIN
#define CONFIG_AUDIO_ENC_SAMPLE_SOURCE AUDIO_ENC_SAMPLE_SOURCE_MIC //录音输入源选择
将MIC的数字信号采集后推到DAC播放,注意:如果需要播放两路MIC,DAC分别对应的是DACL和DACR,要留意芯片封装是否有DACR引脚出来, 而且要使能DAC的双通道输出,DAC如果采用差分输出方式也只会听到第一路MIC的声音。
录音打开解码器
/****************打开解码DAC器*******************/
req.dec.cmd = AUDIO_DEC_OPEN;
req.dec.volume = __this->volume;
req.dec.output_buf_len = 4 * 1024;
req.dec.channel = channel;
req.dec.sample_rate = sample_rate;
req.dec.vfs_ops = &recorder_vfs_ops;
req.dec.dec_type = "pcm";
req.dec.sample_source = CONFIG_AUDIO_DEC_PLAY_SOURCE;
req.dec.file = (FILE *)&__this->save_cbuf;
/* req.dec.attr = AUDIO_ATTR_LR_ADD; */ //左右声道数据合在一起,封装只有DACL但需要测试两个MIC时可以打开此功能
err = server_request(__this->dec_server, AUDIO_REQ_DEC, &req);
if (err) {
goto __err;
}
req.dec.cmd = AUDIO_DEC_START;
server_request(__this->dec_server, AUDIO_REQ_DEC, &req);
录音打开编码器
/****************打开编码器*******************/
memset(&req, 0, sizeof(union audio_req));
//BIT(x)用来区分上层需要获取哪个通道的数据
if (channel == 2) {
req.enc.channel_bit_map = BIT(CONFIG_AUDIO_ADC_CHANNEL_L) | BIT(CONFIG_AUDIO_ADC_CHANNEL_R);//如BIT(0) 对应的是MIC0 BIT(1)对应的是MIC1
} else {
req.enc.channel_bit_map = BIT(CONFIG_AUDIO_ADC_CHANNEL_L);
}
req.enc.frame_size = sample_rate / 100 * 4 * channel; //收集够多少字节PCM数据就回调一次fwrite
req.enc.output_buf_len = req.enc.frame_size * 3; //底层缓冲buf至少设成3倍frame_size
req.enc.cmd = AUDIO_ENC_OPEN;
req.enc.channel = channel;
req.enc.volume = __this->gain;
req.enc.sample_rate = sample_rate;
req.enc.format = "pcm";
req.enc.sample_source = __this->sample_source;
req.enc.vfs_ops = &recorder_vfs_ops;
req.enc.file = (FILE *)&__this->save_cbuf;
if (channel == 1 && !strcmp(__this->sample_source, "mic") && (sample_rate == 8000 || sample_rate == 16000)) {
req.enc.use_vad = 1; //打开VAD断句功能
req.enc.dns_enable = 1; //打开降噪功能
req.enc.vad_auto_refresh = 1; //VAD自动刷新
}
err = server_request(__this->enc_server, AUDIO_REQ_ENC, &req);
if (err) {
goto __err1;
}
MIC或者LINEIN模拟直通到DAC,不需要软件参与。
因为AUDIO ADC数字模块上的使用限制,例如在使用通道2的时候,数字通道0和1一定要打开,所以在多路AD复用的情况下,建议把四路数字通道都打开,驱动里会自动分离各个通道的数据,具体使用方法如下:
请求enc_server 打开时需要增加参数req.enc.channel_bit_map = BIT(x),x代表要使用的哪一个通道。注意,如果上面的配置all_channel_open = 0,一定要将所有req.enc.channel_bit_map = 0;
AUDIO ADC多路复用使用过程中,若想动态调整增益,取值范围是0-100,需要使用该函数void adc_multiplex_set_gain(const char *source, u8 channel_bit_map, u8 gain), source 取值”mic”或者”linein”。
录音到SD卡
//录音文件到SD卡
static int recorder_to_file(int sample_rate, u8 channel)
{
union audio_req req = {0};
__this->run_flag = 1;
__this->direct = 0;
char time_str[64] = {0};
char file_name[100] = {0};
u8 dir_len = 0;
struct tm timeinfo = {0};
time_t timestamp = time(NULL) + 28800;
localtime_r(×tamp, &timeinfo);
strcpy(time_str, CONFIG_ROOT_PATH"RECORDER/\\U");
dir_len = strlen(time_str);
strftime(time_str + dir_len, sizeof(time_str) - dir_len, "%Y-%m-%dT%H-%M-%S.wav", &timeinfo);
log_i("recorder file name : %s\n", time_str);
memcpy(file_name, time_str, dir_len);
for (u8 i = 0; i < strlen(time_str) - dir_len; ++i) {
file_name[dir_len + i * 2] = time_str[dir_len + i];
}
req.enc.cmd = AUDIO_ENC_OPEN;
req.enc.channel = channel;
req.enc.volume = __this->gain;
req.enc.frame_size = 8192;
req.enc.output_buf_len = req.enc.frame_size * 10;
req.enc.sample_rate = sample_rate;
req.enc.format = "wav";
req.enc.sample_source = __this->sample_source;
req.enc.msec = CONFIG_AUDIO_RECORDER_DURATION;
req.enc.file = __this->fp = fopen(file_name, "w+");
/* req.enc.sample_depth = 24; //IIS支持采集24bit深度 */
if (channel == 4) {
req.enc.channel_bit_map = 0x0f;
} else if (channel == 2) {
req.enc.channel_bit_map = BIT(CONFIG_AUDIO_ADC_CHANNEL_L) | BIT(CONFIG_AUDIO_ADC_CHANNEL_R);
} else {
req.enc.channel_bit_map = BIT(CONFIG_AUDIO_ADC_CHANNEL_L);
}
return server_request(__this->enc_server, AUDIO_REQ_ENC, &req);
}
4.打断唤醒模式
打断唤醒使用方法,通过打断唤醒算法通过唤醒词进行唤醒
详情查看AISP打断唤醒文档
2.4.5. 编解码流程
详情请查看音频编解码文档AUDIO_ENC和AUDIO_DEC