音频录制与播放-STM32F779I-EVAL

概述

声音的录制和播放主要用到了DFSDM和SAI两个外设。

  • DFSDM是将外部sigma-delta调制器的高速数字比特流,通过数字滤波和抽取得到高分辨率PCM数据的硬件模块。

  • SAI(Serial Audio Interface,串行音频接口) 在 STM32 中被广泛用于连接外部音频设备。通过 SAI,MCU 可以与放大器、ADC、DAC、音频处理器等外部音频芯片进行高效的数据交互。

在录音流程中,数字麦克风采集到的 PDM 数据 送入 DFSDM 模块,由 DFSDM 转换为 PCM 数据流。应用层通过 DMA + 回调函数 的方式,将转换后的 PCM 数据拷贝到用户指定的录音缓冲区,从而完成音频录制。

在播放流程中,应用层同样通过 DMA + 回调机制,将待播放的 PCM 数据填充到播放缓冲区,由 SAI 外设 按音频时序输出到外部音频设备,实现语音播放。

代码实现

添加依赖代码

  1. BSP文件
  • wm8994:音频Codec
  • audio:音频录制与播放的BSP实现
  • sdram:初始化/配置SDRAM
  1. HAL文件

DFSDM 与 SAI 外设可通过 CubeMX 启用,以自动生成对应 HAL 文件。

由于 音频初始化完全由 BSP 完成,在 CubeMX 的 Project Manager 中不勾选生成初始化代码。

如果 BSP 还依赖其它 HAL 模块(如 UART、FMC),可通过相同方式添加,或直接拷贝对应 HAL 文件。

录音代码

缓冲区与状态定义

复制代码
#define PCM_BUFFER_SIZE         1024
#define RECORD_BUFFER_SIZE      409600
#define SCRATCH_BUFF_SIZE       512

/* 录音状态机定义 */
#define RECORD_STATE_IDLE       0   // 空闲
#define RECORD_STATE_RECORDING  1   // 正在录音
#define RECORD_STATE_COMPLETED  2   // 录音完成

/* DMA 使用的音频数据缓冲区 */
static uint16_t pcm_buffer[PCM_BUFFER_SIZE * 2];

/* 最终录音数据缓存,放在 SDRAM 中 */
static __attribute__((section(".SD_RAM"))) uint16_t record_buffer[RECORD_BUFFER_SIZE];

/* DFSDM 使用的 Scratch buffer */
static int32_t Scratch[SCRATCH_BUFF_SIZE];

/* 当前已录入的样本数(单位:uint16_t) */
static uint32_t record_buffer_size = 0;

/* 当前录音状态 */
static uint8_t record_state = RECORD_STATE_IDLE;

半传输完成回调

复制代码
void BSP_AUDIO_IN_HalfTransfer_CallBack(void)
{
    /* 将 DMA buffer 前半部分拷贝到录音 buffer */
    memcpy(record_buffer + record_buffer_size,
           pcm_buffer,
           PCM_BUFFER_SIZE * sizeof(uint16_t));

    /* 更新已录入样本数 */
    record_buffer_size += PCM_BUFFER_SIZE;

    /* 如果录音 buffer 已满,停止录音 */
    if(record_buffer_size >= RECORD_BUFFER_SIZE)
    {
        BSP_AUDIO_IN_Stop();
        record_state = RECORD_STATE_COMPLETED;
    }
}

全传输完成回调

复制代码
void BSP_AUDIO_IN_TransferComplete_CallBack(void)
{
    /* 将 DMA buffer 后半部分拷贝到录音 buffer */
    memcpy(record_buffer + record_buffer_size,
           pcm_buffer + PCM_BUFFER_SIZE,
           PCM_BUFFER_SIZE * sizeof(uint16_t));

    /* 更新已录入样本数 */
    record_buffer_size += PCM_BUFFER_SIZE;

    /* 如果录音 buffer 已满,停止录音 */
    if(record_buffer_size >= RECORD_BUFFER_SIZE)
    {
        BSP_AUDIO_IN_Stop();
        record_state = RECORD_STATE_COMPLETED;
    }
}

启动录音

复制代码
uint8_t audio_record(void)
{
    uint8_t status;

    /* 清空录音计数 */
    record_buffer_size = 0;

    /* 设置状态为录音中 */
    record_state = RECORD_STATE_RECORDING;

    /* 初始化音频输入参数:采样率、位宽、通道数 */
    status = BSP_AUDIO_IN_Init(DEFAULT_AUDIO_IN_FREQ,
                               DEFAULT_AUDIO_IN_BIT_RESOLUTION,
                               DEFAULT_AUDIO_IN_CHANNEL_NBR);
    if(status != AUDIO_OK)
    {
        return STATUS_AUDIO_INIT_FAILURE;
    }

    /* 分配 DFSDM 使用的 Scratch buffer */
    status = BSP_AUDIO_IN_AllocScratch(Scratch, SCRATCH_BUFF_SIZE);
    if(status != AUDIO_OK)
    {
        return STATUS_FAILURE;
    }

    /* 启动 DMA 录音,DMA 会循环写入 pcm_buffer */
    status = BSP_AUDIO_IN_Record((uint16_t *)pcm_buffer,
                                 PCM_BUFFER_SIZE * 2);
    if(status != AUDIO_OK)
    {
        return STATUS_AUDIO_RECORD_FAILURE;
    }

    return STATUS_SUCCESS;
}

获取录音结果

复制代码
uint8_t audio_record_buffer_get(AUDIO_DATA *audio_data)
{
    if(!audio_data)
    {
        return STATUS_AUDIO_NULL_PTR;
    }

    /* 只有在录音完成状态下才允许读取数据 */
    if(record_state == RECORD_STATE_COMPLETED)
    {
        audio_data->data_ptr = record_buffer;
        audio_data->size     = record_buffer_size;
        return STATUS_SUCCESS;
    }

    return STATUS_FAILURE;
}

播放代码

缓冲区与状态定义

复制代码
#include "main.h"

#define PCM_BUFFER_SIZE         1024
#define RECORD_BUFFER_SIZE      409600
#define SCRATCH_BUFF_SIZE       512

#define RECORD_STATE_IDLE       0   // 空闲
#define RECORD_STATE_RECORDING  1   // 正在录音
#define RECORD_STATE_COMPLETED  2   // 录音完成

/* DMA 使用的音频数据缓冲区 */
static uint16_t pcm_buffer[PCM_BUFFER_SIZE * 2];

/* 最终录音数据缓存,放在 SDRAM 中
 * 用于保存完整录音结果
 */
static __attribute__((section(".SD_RAM")))
uint16_t record_buffer[RECORD_BUFFER_SIZE];

/* DFSDM 使用的 Scratch buffer */
static int32_t Scratch[SCRATCH_BUFF_SIZE];

/* 当前已录入的样本数(单位:uint16_t) */
static uint32_t record_buffer_size = 0;

/* 当前录音状态 */
static uint8_t record_state = RECORD_STATE_IDLE;

半传输完成回调

复制代码
void BSP_AUDIO_IN_HalfTransfer_CallBack(void)
{
    /* 将 DMA buffer 前半部分拷贝到录音 buffer */
    memcpy(record_buffer + record_buffer_size,
           pcm_buffer,
           PCM_BUFFER_SIZE * sizeof(uint16_t));

    /* 更新已录入样本数 */
    record_buffer_size += PCM_BUFFER_SIZE;

    /* 如果录音 buffer 已满,停止录音 */
    if(record_buffer_size >= RECORD_BUFFER_SIZE)
    {
        BSP_AUDIO_IN_Stop();
        record_state = RECORD_STATE_COMPLETED;
    }
}

全传输完成回调
```c
void BSP_AUDIO_IN_TransferComplete_CallBack(void)
{
    /* 将 DMA buffer 后半部分拷贝到录音 buffer */
    memcpy(record_buffer + record_buffer_size,
           pcm_buffer + PCM_BUFFER_SIZE,
           PCM_BUFFER_SIZE * sizeof(uint16_t));

    /* 更新已录入样本数 */
    record_buffer_size += PCM_BUFFER_SIZE;

    /* 如果录音 buffer 已满,停止录音 */
    if(record_buffer_size >= RECORD_BUFFER_SIZE)
    {
        BSP_AUDIO_IN_Stop();
        record_state = RECORD_STATE_COMPLETED;
    }
}

启动录音

复制代码
uint8_t audio_record(void)
{
    uint8_t status;

    /* 清空录音计数 */
    record_buffer_size = 0;

    /* 设置状态为录音中 */
    record_state = RECORD_STATE_RECORDING;

    /* 初始化音频输入参数:采样率、位宽、通道数 */
    status = BSP_AUDIO_IN_Init(DEFAULT_AUDIO_IN_FREQ,
                               DEFAULT_AUDIO_IN_BIT_RESOLUTION,
                               DEFAULT_AUDIO_IN_CHANNEL_NBR);
    if(status != AUDIO_OK)
    {
        return STATUS_AUDIO_INIT_FAILURE;
    }

    /* 分配 DFSDM 使用的 Scratch buffer */
    status = BSP_AUDIO_IN_AllocScratch(Scratch, SCRATCH_BUFF_SIZE);
    if(status != AUDIO_OK)
    {
        return STATUS_FAILURE;
    }

    /* 启动 DMA 录音,DMA 会循环写入 pcm_buffer */
    status = BSP_AUDIO_IN_Record((uint16_t *)pcm_buffer,
                                 PCM_BUFFER_SIZE * 2);
    if(status != AUDIO_OK)
    {
        return STATUS_AUDIO_RECORD_FAILURE;
    }

    return STATUS_SUCCESS;
}

获取录音结果

复制代码
uint8_t audio_record_buffer_get(AUDIO_DATA *audio_data)
{
    if(!audio_data)
    {
        return STATUS_AUDIO_NULL_PTR;
    }

    /* 只有在录音完成状态下才允许读取数据 */
    if(record_state == RECORD_STATE_COMPLETED)
    {
        audio_data->data_ptr = record_buffer;
        audio_data->size     = record_buffer_size;
        return STATUS_SUCCESS;
    }

    return STATUS_FAILURE;
}

添加中断服务函数

复制代码
void AUDIO_SAIx_DMAx_IRQHandler(void)
{
  HAL_DMA_IRQHandler(haudio_out_sai.hdmatx);
}

void AUDIO_DFSDM_DMAx_LEFT_IRQHandler(void)
{
  HAL_DMA_IRQHandler(haudio_in_dfsdm_leftfilter.hdmaReg);
}

void AUDIO_DFSDM_DMAx_RIGHT_IRQHandler(void)
{
  HAL_DMA_IRQHandler(haudio_in_dfsdm_rightfilter.hdmaReg);
}

SDRAM 链接脚本配置

复制代码
/* Memories definition */
MEMORY
{
  RAM    (xrw)    : ORIGIN = 0x20000000,   LENGTH = 512K
  FLASH    (rx)    : ORIGIN = 0x8000000,   LENGTH = 2048K
  SD_RAM (xrw)   : ORIGIN = 0xC0000000,    LENGTH = 8192K
}

.sd_ram :
{
    *(.SD_RAM);
} >SD_RAM
相关推荐
wzf@robotics_notes3 小时前
振动控制提升 3D 打印机器性能
嵌入式硬件·算法·机器人
罗兰Yolanda4 小时前
影视后期全流程的核心软件及工作站配置方案推荐
计算机视觉·音视频
Jia shuheng5 小时前
#ifdef __cplusplus extern “C“ #endif的作用
c语言·嵌入式硬件
破晓单片机5 小时前
STM32单片机分享:智能净化器系统
stm32·单片机·嵌入式硬件·智能家居
良月十二2655 小时前
ORCAD导出BOM
嵌入式硬件
嗯嗯=6 小时前
STM32单片机学习篇6
stm32·单片机·学习
秋深枫叶红6 小时前
嵌入式第四十九篇——ARM系列——IMX6ULL开发板
arm开发·嵌入式硬件
电子科技圈7 小时前
芯科科技助力涂鸦智能推出免编码AIoT平台创新智能照明开发
科技·嵌入式硬件·mcu·物联网·智能家居·智能硬件·iot
持梦远方7 小时前
Arduino 学习-第3课:PWM 实现 LED 呼吸灯效果
stm32·单片机