音频录制与播放-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
相关推荐
BackCatK Chen12 小时前
第 8 篇:TMC2240 电机正反转实现|DIR 引脚控制 + 代码优化(稳定不抖动)
stm32·单片机·嵌入式硬件·保姆级教程·电机正反转·tmc2240·dir引脚控制
星马梦缘12 小时前
EDA彩灯电路绘制
单片机·嵌入式硬件·物联网·pcb·eda·嘉立创
sweetone14 小时前
LINN莲CLASSIK桌面音响微修
经验分享·音视频
Hello_Embed17 小时前
libmodbus 移植 STM32(USB 串口后端篇)
笔记·stm32·单片机·嵌入式·freertos·libmodbus
晚霞的不甘18 小时前
CANN 编译器深度解析:UB、L1 与 Global Memory 的协同调度机制
java·后端·spring·架构·音视频
VekiSon18 小时前
Linux内核驱动——杂项设备驱动与内核模块编译
linux·c语言·arm开发·嵌入式硬件
美狐美颜SDK开放平台19 小时前
多终端适配下的人脸美型方案:美颜SDK工程开发实践分享
人工智能·音视频·美颜sdk·直播美颜sdk·视频美颜sdk
来自晴朗的明天19 小时前
14、光耦隔离电路(EL3H7)
单片机·嵌入式硬件·硬件工程
G***技19 小时前
杰和IB3-272:以低功耗高性能打造新一代工业智能交互核心
单片机·嵌入式硬件·物联网
MAR-Sky21 小时前
keil5中数据的不同定义和单片机(以stc8为例)里的对应关系(idata,xdata,data,code)
单片机·嵌入式硬件