音频录制与播放-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
相关推荐
码上观世界1 小时前
一张图 + 一段音频,生成口型同步、有表情的数字人
音视频
Horn Still Sounds2 小时前
IMX6ULL裸机|I2C通信协议 + ADC模数转换学习笔记
arm开发·笔记·单片机·嵌入式硬件
智感子2 小时前
无人值守称重:地磅系统架构与流程
嵌入式硬件
新晨单片机设计4 小时前
S011C-基于STM32智能灯光控制系统(光强、声音、人体)【Proteus仿真+Keil程序+原理图】
stm32·嵌入式硬件·proteus·智能灯光控制系统
无敌贵点大王4 小时前
RTThread学习记录13——关于要使用一个外设,RTThread与cubemx到底要怎么配合?
c语言·stm32·学习·rtthread
鲲穹AI种草4 小时前
自媒体矩阵批量剪辑怎么选?鲲剪短视频批量处理工具横向评测
人工智能·音视频·媒体
可乐鸡翅yeah_4 小时前
网页端 M3U8 播放器性能监控,怎么看播放卡顿指标
javascript·ffmpeg·音视频·safari·m3u8
可乐鸡翅yeah_4 小时前
M3U8 防盗链 URL‑Token 签名,新手开发实操避坑
开发语言·javascript·ios·音视频·safari
昨日之日20065 小时前
yovoice:本地配音工具箱,支持音色克隆与情绪控制,专为旁白、有声书和视频配音打造
人工智能·音视频
狗凯之家源码网5 小时前
2026 版短视频与视频号去水印源码深度评测
音视频