Skip to content

概念先知道

  • Codec(编解码器):负责模拟音频信号与数字 PCM 数据之间的转换——录音是麦克风模拟信号 → 数字 PCM,播放是数字 PCM → 喇叭模拟信号。BL616/BL618 内置音频编解码器(xcodec)。
  • PCM:音频的原始数字格式,记录每个采样点的幅值;例程用 16KHz、16bit、单声道 PCM 测试。
  • 采样率/位深:每秒采多少个点(16K = 16000 点/秒)和每个点用多少位表示(16bit)。麦克风与喇叭两端必须一致。
  • DMA 传输:编解码数据量大,例程通过 DMA 通道异步读写(xcodec_output_write_async / xcodec_input_read_async),避免 CPU 忙等。

例程功能简介

本页对应博流官方 SDK 的 codec 例程(examples/audio/codec),演示 Ai-M6x 内置音频编解码器的使用:

  • codec_speaker:初始化 xcodec 输出通道,通过 DMA 播放一段内置 16K/16bit/单声道 PCM 测试音;
  • codec_mic:初始化输入通道,采集麦克风数据并统计打印;
  • codec_loop:回环测试(当前版本 README 注明 loopback 尚待完善);
  • codec_eq:EQ/增益/限幅等音频处理参数调试;
  • 例程使用 xcodec 驱动 APIxcodec_init / xcodec_output_open / xcodec_output_write_async 等)。
  • 同族例程examples/audio/):player(播放器,见「音频播放」页)、minialsa(ALSA 风格接口)。

注意

音频引脚与功放配置需要按板子调整(见 README 的 multimedia_port.c 适配说明:g_pa_delay_2CONFIG_AUDIO_PA_PIN、麦克风/喇叭输入输出引脚等)。使用外挂 ES8388 时需设置 CONFIG_CODEC_USE_ES8388=1

操作步骤

1
进入例程目录

本页需要带麦克风/喇叭的开发板。在终端进入 SDK 的音频编解码例程目录(前提:已按快速开始(Linux)Windows搭建好环境):

cd examples/audio/codec
2
编译工程

执行编译命令。Ai-M62(BL616)与 Ai-M61(BL618)同属一个系列,统一填写引脚最少的 bl616 即可:

make CHIP=bl616 BOARD=bl616dk
3
烧录固件

用 USB 线连接开发板,按住 BOOT 键(Ai-M61-32S-Kit 为 IO2)不放、短按 EN/RST 进入下载模式,然后执行烧录(把串口号换成实际值):

make flash CHIP=bl616 COMX=/dev/ttyUSB0
4
喇叭输出测试

打开串口助手(波特率 2000000),在 bouffalolab /> 提示符输入(例程会播放一段 16K 16bit 单声道 PCM 测试音):

codec_speaker
5
麦克风录音测试

输入 codec_mic 开始采集麦克风数据并打印采样信息(按 Ctrl+C 停止):

codec_mic
6
其他测试命令

回环测试:codec_loop;EQ/增益调试:codec_eq(配合 MSP_PEQMSP_GAIN 等子命令)。

代码执行流程

喇叭播放测试的完整流程如下(麦克风采集流程类似,方向相反):

例程调用的 API 介绍

xcodec_init(&codec, 0)

初始化音频编解码器设备。

参数

  • codecxcodec_dev_t 设备结构体
  • id:设备编号(0)

返回值0 成功;负值失败

xcodec_output_open(&codec, &ch, 0)

打开输出(播放)通道,配置采样率、位深、声道等参数(在 xcodec_ch_cfg 中设置)。

参数

  • codec:编解码器设备
  • ch:输出通道句柄
  • id:通道编号(0)

返回值0 成功;负值失败

xcodec_output_write_async(&ch, buf, len)

异步写入 PCM 数据到输出通道(内部经 DMA 送往喇叭),写满后返回实际写入长度,需循环调用直到写完。

参数

  • ch:输出通道句柄
  • buf:PCM 数据
  • len:待写入长度

返回值:本次实际写入长度

xcodec_input_open / xcodec_input_read_async(&codec, &ch, 0) / (&ch, buf, len)

打开输入(录音)通道并异步读取麦克风采集的 PCM 数据,与输出接口对称。

参数

  • codec / ch:设备与输入通道
  • buf / len:接收缓冲区与期望长度

返回值open 返回 0 成功;read_async 返回实际读取长度

xcodec_uninit(&codec)

释放编解码器资源,测试结束后调用。

参数

  • codec:编解码器设备

返回值:无

完整代码

以下为 codec/main.c 完整源码,与官方示例(examples/audio/codec)一致(实际播放/录音逻辑在 app/ 目录的 codec_speaker.ccodec_mic.c 等文件中),默认折叠,点击展开:

📜 点击展开 codec/main.c 完整代码
c
/****************************************************************************
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include "FreeRTOS.h"
#include "task.h"
#include "timers.h"

#include <lwip/tcpip.h>
#include <lwip/sockets.h>
#include <lwip/netdb.h>

#include "bl_fw_api.h"
#include "fhost_api.h"
#include "wifi_mgmr_ext.h"
#include "wifi_mgmr.h"

#include "bflb_irq.h"
#include "bflb_uart.h"

#include "bl616_glb.h"
#include "rfparam_adapter.h"

#include "board.h"
#include "shell.h"

#define DBG_TAG "MAIN"
#include "log.h"
#include "async_event.h"

struct bflb_device_s *gpio;

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

#define WIFI_STACK_SIZE  (1536)
#define TASK_PRIORITY_FW (16)

/****************************************************************************
 * Private Types
 ****************************************************************************/

/****************************************************************************
 * Private Data
 ****************************************************************************/

static struct bflb_device_s *uart0;


extern void shell_init_with_task(struct bflb_device_s *shell);
extern void wifi_event_handler(async_input_event_t ev, void *priv);

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

/****************************************************************************
 * Functions
 ****************************************************************************/

void wifi_start_firmware_task(void *param)
{
    LOG_I("Starting wifi ...\r\n");

    /* set ble controller EM Size */

    GLB_Set_EM_Sel(GLB_WRAM160KB_EM0KB);

    if (0 != rfparam_init(NULL, NULL, NULL)) {
        LOG_I("PHY RF init failed!\r\n");
        vTaskDelete(NULL);
    }

    async_register_event_filter(EV_WIFI, wifi_event_handler, NULL);


    wifi_task_create();

    LOG_I("Starting fhost ...\r\n");
    fhost_init();

    vTaskDelete(NULL);
}

void wifi_event_handler(async_input_event_t ev, void *priv)
{
    uint32_t code = ev->code;

     switch (code) {
        case CODE_WIFI_ON_INIT_DONE: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_INIT_DONE\r\n", __func__);
            wifi_mgmr_task_start();
        } break;
        case CODE_WIFI_ON_MGMR_DONE: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_MGMR_DONE\r\n", __func__);
        } break;
        case CODE_WIFI_ON_SCAN_DONE: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_SCAN_DONE\r\n", __func__);
            wifi_mgmr_sta_scanlist();
        } break;
        case CODE_WIFI_ON_CONNECTED: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_CONNECTED\r\n", __func__);
            void mm_sec_keydump();
            mm_sec_keydump();
        } break;
        #ifdef CODE_WIFI_ON_GOT_IP_ABORT
        case CODE_WIFI_ON_GOT_IP_ABORT: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_GOT_IP_ABORT\r\n", __func__);
        } break;
        #endif
        #ifdef CODE_WIFI_ON_GOT_IP_TIMEOUT
        case CODE_WIFI_ON_GOT_IP_TIMEOUT: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_GOT_IP_TIMEOUT\r\n", __func__);
        } break;
        #endif
        case CODE_WIFI_ON_GOT_IP: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_GOT_IP\r\n", __func__);
        } break;
        case CODE_WIFI_ON_DISCONNECT: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_DISCONNECT\r\n", __func__);
        } break;
        case CODE_WIFI_ON_AP_STARTED: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_AP_STARTED\r\n", __func__);
        } break;
        case CODE_WIFI_ON_AP_STOPPED: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_AP_STOPPED\r\n", __func__);
        } break;
        case CODE_WIFI_ON_AP_STA_ADD: {
            LOG_I("[APP] [EVT] [AP] [ADD] %lld\r\n", xTaskGetTickCount());
        } break;
        case CODE_WIFI_ON_AP_STA_DEL: {
            LOG_I("[APP] [EVT] [AP] [DEL] %lld\r\n", xTaskGetTickCount());
        } break;
        default: {
            LOG_I("[APP] [EVT] Unknown code %u \r\n", code);
        }
    }
}

int main(void)
{
    board_init();

    uart0 = bflb_device_get_by_name("uart0");
    shell_init_with_task(uart0);

    tcpip_init(NULL, NULL);
    xTaskCreate(wifi_start_firmware_task, "wifi init", 1024, NULL, 10, NULL);
#if defined(CONFIG_CODEC_USE_I2S)
    extern msp_i2s_port_init(void);
    msp_i2s_port_init();
#endif
    vTaskStartScheduler();

    while (1) {
    }
}

FAQ

codec_speaker 没有声音

检查喇叭/PA 接线与 multimedia_port.c 的引脚配置(CONFIG_AUDIO_PA_PINg_pa_delay_2);再确认输出引脚(OUTPUT_POSITIVE/NEGATIVE_PIN)与板子一致,内置 codec 与外部 ES8388 不要混用配置。

codec_mic 采不到数据

确认麦克风接在输入引脚(INPUT_POSITIVE/NEGATIVE_PIN)上,且配置的是板子实际使用的 codec(内置或 ES8388);不使用的差分输入引脚建议设为 255。

采样率与预期不符

例程使用 16KHz/16bit/单声道;如需其他采样率,修改 codec_speaker.c / codec_mic.c 中通道配置(xcodec_ch_cfg)的采样率与位深,注意内置 codec 支持的范围。

遇到问题?

如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

Released under the MIT License. Build Time 2026-09-11 14:52:23