音频录制与播放-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
相关推荐
REDcker2 天前
WebCodecs VideoDecoder 的 hardwareAcceleration 使用
前端·音视频·实时音视频·直播·webcodecs·videodecoder
gihigo19982 天前
基于TCP协议实现视频采集与通信
网络协议·tcp/ip·音视频
Lester_11012 天前
STM32霍尔传感器输入口设置为复用功能输入口时,还能用GPIO函数直接读取IO的状态吗
stm32·单片机·嵌入式硬件·电机控制
LCG元2 天前
低功耗显示方案:STM32L0驱动OLED,动态波形绘制与优化
stm32·嵌入式硬件·信息可视化
三佛科技-187366133972 天前
120W小体积碳化硅电源方案(LP8841SC极简方案12V10A/24V5A输出)
单片机·嵌入式硬件
z20348315202 天前
STM32F103系列单片机定时器介绍(二)
stm32·单片机·嵌入式硬件
古译汉书2 天前
【IoT死磕系列】Day 7:只传8字节怎么控机械臂?学习工业控制 CANopen 的“对象字典”(附企业级源码)
数据结构·stm32·物联网·http
山河君2 天前
四麦克风声源定位实战:基于 GCC-PHAT + 最小二乘法实现 DOA
算法·音视频·语音识别·信号处理·最小二乘法·tdoa
Alaso_shuang2 天前
STM32 核心输入、输出模式
stm32·单片机·嵌入式硬件
脚后跟2 天前
AI助力嵌入式物联网项目全栈开发
嵌入式硬件·物联网·ai编程