用DAC播放WAV文件

前言:目前基于智能门锁的开发,需要用到语音,但是由于如果用语音IC的话,声音内容就不方便调整更改,所以我们打算用单片机的DAC功能直接读取WAV文件,再用功放IC放大来实现智能语音。

步骤一:用朗读女获取WAV文件

用DAC播放WAV文件_第1张图片

用DAC播放WAV文件_第2张图片

有时候无法勾选WAV文件,可能是由于没有下载发音人语音导致的。

步骤二:wav文件转PCM文件。

WAV:wav是一种无损的音频文件格式,WAV符合 PIFF(Resource Interchange File Format)规范。所有的WAV都有一个文件头,这个文件头音频流的编码参数。WAV对音频流的编码没有硬性规定,除了PCM之外,还有几乎所有支持ACM规范的编码都可以为WAV的音频流进行编码。

PCM:PCM(Pulse Code Modulation—-脉码调制录音)。所谓PCM录音就是将声音等模拟信号变成符号化的脉冲列,再予以记录。PCM信号是由[1]、[0]等符号构成的数字信号,而未经过任何编码和压缩处理。与模拟信号比,它不易受传送系统的杂波及失真的影响。动态范围宽,可得到音质相当好的影响效果。

简单来说pcm是无损wav文件中音频数据的一种编码方式,但wav还可以用其它方式编码 。

WAV文件头部解析:

用DAC播放WAV文件_第3张图片

00-03    52 49 46 46:RIFF的标志

04-07    76 43 01 00 :文件的长度

08-0B    57 41 56 45  :WAVE的标志

0C-0F   66 6D 74 20  :fmt的标志

10-13   12 00 00 00   :编码的格式类别,10H代表PCM形式。

14-15    01  00  :字块总数

16-17   01  00   : 通道数  1为单声道,2为双声道

18-1B  80 3E 00 00 :采样率:每秒采集二进制的位数。

1C-1F  00 7D 00 00 :每秒播放的字节数 = 通道数 x 每秒采集二进制的位数 x 采集数据的位数 / 8.

20-21  02 00 :每个样点的字节数

22-23  01 00 :每个样点的数据位数

24-27 00 00 64 61 :data的标志

步骤三:把处理生成的PCM文件,合成一个bin文件烧录到外部的flash中,然后MCU根据地址去读相应的数据

void play_wave(uint8_t index)
{
    uint8_t end = 0;
    key_flag = 0;
    int16_t *sample_ptr;
    uint16_t i = 0;
    speaker_init();
    HAL_Delay(100);
    HAL_TIM_Base_Start_IT(&htim6);  //打开定时器
    
    switch(index) {    //外部flash的地址的索引
        case ZERO:
            chunk_count = ZERO_SIZE / 256; 
            chunk_residual = ZERO_SIZE % 256;        
            flash_addr = ZERO_ADDR;
            break;
        case ONE:
            chunk_count = ONE_SIZE / 256; 
            chunk_residual = ONE_SIZE % 256;        
            flash_addr = ONE_ADDR;
            break;

}    
    flash_read_data(flash_addr, (uint8_t *)dma_buf, 256);    //读外部flash的数据
    flash_read_data(flash_addr + 256, (uint8_t *)&dma_buf[128], 256);
    sample_ptr = (int16_t *)dma_buf;
    for(i=0; i<256; i++) {
        sample_ptr[i] = (sample_ptr[i] >> volume) + 2048;
    }
    consume_count = 2;
    HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, (uint32_t *)dma_buf, 256, DAC_ALIGN_12B_R);   //开始DAC的DMA转换
    while(1) {      //设置一个标志位,采用乒乓的方式自动去读外部flash的数据
        if(wave_dma_status == 1) {
            end = fill_dma_buffer_from_flash(&hdac, 256, flash_addr);
            wave_dma_status = 0;
        } else if(wave_dma_status == 2) {
            end = fill_dma_buffer_from_flash(&hdac, 0, flash_addr);
            wave_dma_status = 0;
        }
        if(end) {
            HAL_TIM_Base_Stop_IT(&htim6);
            break;            
        }
    }
    HAL_GPIO_WritePin(Speaker_Pwr_GPIO_Port,Speaker_Pwr_Pin,GPIO_PIN_RESET);
}

uint8_t fill_dma_buffer_from_flash(DAC_HandleTypeDef *thdac, uint16_t offset, uint32_t addr)
{
    uint32_t src_ptr = addr;
    uint8_t *dst_ptr = (uint8_t *)dma_buf;
    uint16_t i = 0;
    dst_ptr += offset;
    src_ptr += consume_count * 256;
    int16_t *sample_ptr = (int16_t *)dst_ptr;
    if(consume_count == chunk_count) {   //分几种情况读完音频文件
        if(chunk_residual != 0) {
            flash_read_data(src_ptr, dst_ptr, chunk_residual);            //second
            for(i=0; i                 sample_ptr[i] = (sample_ptr[i] >> volume) + 2048;    
            }
            
        } else {
            consume_count = 0;                                            //end
            HAL_DAC_Stop_DMA(thdac, DAC_CHANNEL_1);
            return 1;
        }        
    } else if(consume_count == (chunk_count + 1)) {                        //end
        consume_count = 0;
        HAL_DAC_Stop_DMA(thdac, DAC_CHANNEL_1);
        return 1;        
    } else {
        flash_read_data(src_ptr, dst_ptr, 256);                            //first
        for(i=0; i<128; i++) {
            sample_ptr[i] = (sample_ptr[i] >> volume) + 2048;
        }
    }    
    consume_count++;    
    return 0;
}
实现wav文件的音频播放。

你可能感兴趣的:(MCU)