Skip to content

音频使用说明

本文主要介绍音频的开发和使用:包含总体架构、核心接口说明、CODEC 配置、Demo:TTS 与播放器、Demo:全双工与打电话


目录

  1. 总体架构
  2. 核心接口说明
  3. CODEC 配置
  4. Demo:TTS 与播放器
  5. Demo:全双工与打电话

1. 总体架构

1.1 分层结构

依赖方向自上而下:应用层 → 业务组件 / 语音合成 → 音频框架 → CODEC 驱动 → api 硬件层。

  • 应用层demo_tts.cdemo_player.cdemo_audio_loop.cdemo_call.c
  • 业务组件 / 语音合成
    • components/call(VoLTE 通话封装)
    • api/yopen_tts.h(TTS 引擎,输出 PCM)
  • 音频框架audio_playeraudio_recorderaudio_decoderaudio_encoder
    • audio_device.c(设备抽象)⇄ audio_config.c(引脚 / CODEC 参数表)
  • CODEC 驱动es8311.c(I2C)、gx8006.c(UART)、pwm.cspicodec.c、纯 I2S(TM8211 / ES7111)
  • api 硬件接口层yopen_i2syopen_i2cyopen_pwmaudyopen_spiaudyopen_volteyopen_voice

1.2 播放数据流

%%{init: {"fontFamily": "Microsoft YaHei, PingFang SC, Noto Sans CJK SC, sans-serif"}}%%

flowchart LR
    A["数据源<br/>文件 / HTTP / TTS / 网络"] --> B["auReader_t<br/>read / is_eof 回调"]
    B --> C["auDecoder_t<br/>MP3 / OPUS / AMR / PCM"]
    C --> D["auPlayer_t<br/>解码任务"]
    D --> E["audevPlayOps_t.get_data"]
    E --> F["audio_device.c<br/>audioPlayerPlayWork"]
    F --> G{"CODEC 类型"}
    G -->|I2S 系| H["yopen_i2s_write"]
    G -->|PWM| I["yopen_pwmaud_write"]
    G -->|SPI| J["yopen_spiaud_write"]
    H --> K["喇叭 / PA"]
    I --> K
    J --> K

1.3 录音数据流

%%{init: {"fontFamily": "Microsoft YaHei, PingFang SC, Noto Sans CJK SC, sans-serif"}}%%

flowchart LR
    A["MIC"] --> B["CODEC ADC"]
    B --> C["yopen_i2s_read<br/>(RX 中断回调 FRAME_END)"]
    C --> D["audio_device.c<br/>audioRecorderWork"]
    D --> E["audevRecordOps_t.put_frame"]
    E --> F["auRecorder_t"]
    F --> G["auEncoder_t<br/>PCM / AMRNB / OPUS / CP 硬编"]
    G --> H["osiPipe_t"]
    H --> I["应用读取"]

1.4 三种音频通道类型

这是理解整个音频框架最关键的一张表。通道类型由 audevPlayType_t / audevRecordType_t 指定,决定了是否启用 CP 侧硬件编解码与降噪。

通道 播放枚举 录音枚举 用途 编解码 降噪
LOCAL AUDEV_PLAY_TYPE_LOCAL AUDEV_RECORD_TYPE_MIC 本地放音乐、放提示音、普通录音 AP 侧软件编解码(libmad / opus / amr)
POC AUDEV_PLAY_TYPE_POC AUDEV_RECORD_TYPE_POC 对讲机、微信对讲,全双工 CP 侧硬件 AMR 编解码
VOICE AUDEV_PLAY_TYPE_VOICE AUDEV_RECORD_TYPE_VOICE VoLTE 打电话 普通 PCM(CP 侧硬件 AMR 编解码在volte 层做) 有,靠 AEC 参考源
  • POC:录音直接返回 AMR 帧(无需 AP 编码器),播放需配 auVoiceAmrDecoderCreate()
  • VOICE:音频侧走 PCM,播放时自动向 AEC 参考源推数据(auVoicePushRefData),上行编码前用 auVoicePopRefData 取参考帧做回声消除。

2. 核心接口说明

2.1 设备层

audio_device.h

接口 说明
bool audevSetPlayVolume(unsigned vol) 设置播放音量,范围 [0,100]
unsigned audevGetPlayVolume(void) 获取播放音量
bool audevSetOutputMute(bool mute) 输出静音/取消静音,不写 NV,上电默认非静音
bool audevIsOutputMute(void) 查询静音状态
bool audevSetMicGain(uint16_t adcGain) 设置 MIC 增益,[0,15]:0=Mute,1=-12dB,7=0dB,15=+16dB
bool audevGetMicGain(uint16_t *adcGain) 获取 MIC 增益
uint32_t audevGetRecordSampleRate(void) 获取当前录音采样率
void audevBindPaCallback(aud_pa_callback_t cb) 注册 PA 开关回调,用于外部功放联动

辅助宏:

/* 20ms 一帧的字节数:16K 单声道 = 640 字节,8K 单声道 = 320 字节 */
#define AUDIO_FRAME_SIZE(sample_rate, channel) \
        ((sample_rate) * sizeof(uint16_t) * (channel) * 20 / 1000)

audevStartPlay / audevStartRecord 属于内部接口,正常业务请使用 player / recorder 层。

2.2 播放器

audio_player.h

auPlayer_t *auPlayerCreate(void);
void        auPlayerDelete(auPlayer_t *d);
void        auPlayerSetEventCallback(auPlayer_t *d, auPlayerEventCallback_t cb, void *ctx);

bool auPlayerStart  (auPlayer_t *d, auDecoder_t *dec);
bool auPlayerStartV2(auPlayer_t *d, auDecoder_t *dec,
                     audevPlayType_t type, auStreamFormat_t format);

bool auPlayerPause (auPlayer_t *d);
bool auPlayerResume(auPlayer_t *d);
bool auPlayerStop  (auPlayer_t *d);
bool auPlayerWaitFinish(auPlayer_t *d, unsigned timeout);   /* YOPEN_WAIT_FOREVER 可用 */

auPlayerStartauPlayerStartV2 的差异(重要)

auPlayerStart auPlayerStartV2
堆栈 与录音编码任务共用静态栈 audio_work_stack 独立 malloc 堆栈(首次调用分配,常驻不释放)
通道类型 固定 LOCAL 可指定 LOCAL / POC / VOICE
适用场景 单工/半双工(只放音) 全双工推荐

结论:只放音用 auPlayerStart,要同时录音就用 auPlayerStartV2

2.3 录音器

audio_recorder.h

auRecorder_t *auRecorderCreate(void);
void          auRecorderDelete(auRecorder_t *d);
void          auRecorderSetEventCallback(auRecorder_t *d, auRecorderEventCallback_t cb, void *ctx);

bool auRecorderStartPipe(auRecorder_t *d,
                         audevRecordType_t   type,    /* MIC / POC / VOICE */
                         audevRecordFormat_t format,  /* PCM / AMRNB / AMRWB / OPUS */
                         const auCodecParamSet_t *params,  /* 末元素 id 必须为 0 */
                         struct osiPipe *pipe);       /* 录音数据输出管道 */

bool auRecorderPause (auRecorder_t *d);
bool auRecorderResume(auRecorder_t *d);
bool auRecorderStop  (auRecorder_t *d);

参数数组示例:

auFrame_t frame = {
    .sample_format = AUSAMPLE_FORMAT_S16,
    .channel_count = 1,
    .sample_rate   = 16000,
};
auCodecParamSet_t params[] = {
    { AU_ENC_PARAM_FORMAT, &frame },
    { 0, NULL }                       /* 结束标记,不可省略 */
};

AMR-NB 还可追加码率参数:

auCodecParamSet_t params[] = {
    { AU_AMBNB_ENC_PARAM_MODE, AU_AMRNB_MODE_1220 },  /* 12.2 kbps */
    { AU_ENC_PARAM_FORMAT,     &frame },
    { 0, NULL }
};

2.4 解码器

audio_decoder.h

auDecoder_t *auDecoderCreate(auReader_t *r, auStreamFormat_t format);
/* CP 硬件 AMR 解码,用于 POC 通道(全双工 + 降噪) */
auDecoder_t *auVoiceAmrDecoderCreate(auReader_t *r, auStreamFormat_t format);

bool auDecoderSetParam(auDecoder_t *d, const auFrame_t *val);
void auDecoderGetParam(auDecoder_t *d, auFrame_t *val, auStreamFormat_t *format);
int  auDecoderDecode(auDecoder_t *d, auFrame_t *frame);
void auDecoderDelete(auDecoder_t *d);

auReader_t 是数据源抽象,应用只需实现 4 个回调(open/close 可为 NULL):

auReader_t reader = {
    .ops = {
        .open   = NULL,
        .close  = NULL,
        .read   = my_read,      /* int (*)(auReader_t*, void *buf, unsigned size) */
        .is_eof = my_is_eof,    /* bool (*)(auReader_t*) */
    },
    .context = my_ctx,          /* 任意上下文,回调里通过 d->context 取回 */
};

支持的流格式 auStreamFormat_t

枚举 说明
AUSTREAM_FORMAT_PCM 裸 PCM(S16LE)
AUSTREAM_FORMAT_MP3 MP3(libmad 软解)
AUSTREAM_FORMAT_OPUS OPUS
AUSTREAM_FORMAT_AMRNB AMR-NB,8 kHz
AUSTREAM_FORMAT_AMRWB AMR-WB,16 kHz。软件编解码不支持,仅 AUDEV_*_TYPE_POC 通道可用

2.5 编码器

audio_encoder.h

auEncoder_t *auAmrnbEncoderCreate(auAmrnbMode_t mode);
auEncoder_t *auOpusEncoderCreate(uint16_t sample_rate);
auEncoder_t *auVoiceEncoderCreate(unsigned sample_rate);  /* CP AMR 硬编 + 降噪,8000/16000 */
void         auEncoderDelete(auEncoder_t *d);
bool         auEncoderSetParam(auEncoder_t *d, int param_id, int val);

AEC 参考源(全局单实例,同一时刻只有一路通道使用):

bool auVoiceRefCreate(void);                                     /* 创建 */
void auVoiceRefDestroy(void);                                    /* 销毁 */
void auVoicePushRefData(const int16_t *pcm, uint32_t bytes);     /* 播放侧推入远端 PCM */
int  auVoicePopRefData(int16_t *pcm, uint32_t bytes);            /* 编码侧取参考帧,-1 表示无参考 */

VOICE 通道下,参考源由通道内部自动创建;auVoicePushRefData 由 voice decoder 解码后自动调用,应用侧通常只需要在上行编码前调用 auVoicePopRefData

2.6 TTS

api/yopen_tts.h

int  yopen_tts_create(yopen_tts_t *tts_ref, const char *resource);
int  yopen_tts_set_config_param(yopen_tts_t tts_ref, YOPEN_TTS_CONFIG_E type, int value);
int  yopen_tts_get_config_param(yopen_tts_t tts_ref, YOPEN_TTS_CONFIG_E type, int *value);
int  yopen_tts_start(yopen_tts_t tts_ref, const char *textString, uint32 textLen,
                     yopen_tts_output_data_cb output_cb,
                     yopen_tts_play_end_cb    end_cb);
bool yopen_tts_is_running(yopen_tts_t tts_ref);
int  yopen_tts_stop(yopen_tts_t tts_ref);
int  yopen_tts_destory(yopen_tts_t tts_ref);

TTS 引擎只负责“文字 → PCM”,不负责出声。 PCM 通过 output_cb 回调吐出来,需要应用自己送进 player 播放。

配置项 YOPEN_TTS_CONFIG_E

配置 取值范围 默认
YOPEN_TTS_CONFIG_SPEED -32768 ~ +32767 YOPEN_TTS_SPEED_NORMAL(0)
YOPEN_TTS_CONFIG_VOICE_PITCH -32768 ~ +32767 YOPEN_TTS_PITCH_NORMAL(0)
YOPEN_TTS_CONFIG_VOLUME -32768 ~ +32767 YOPEN_TTS_VOLUME_MAX
YOPEN_TTS_CONFIG_CODEPAGE GBK(936) / UTF8(65001) / UTF16LE(1200) / BIG5(950) / ASCII(437) GBK
YOPEN_TTS_CONFIG_READ_DIGIT AUTO(0) / AS_NUMBER(1) / AS_VALUE(2) AUTO

资源文件:tts_resource_8k(8 kHz,省 Flash)与 tts_resource_16k(16 kHz,音质更好)。

文本可内嵌控制标记,例如 [p100] 表示插入 100 ms 静音:

#define DEMO_AUD_TTS_TEXT "[p100]上海域格信息技术有限公司,TTS语音播放测试。"

工程开关(在 project/<name>.cmake 中):

set(YOPEN_TTS_ENABLE TRUE)          # 8 kHz 资源
# set(YOPEN_TTS_16K_ENABLE TRUE)    # 16 kHz 资源,音质更好但更占 Flash

2.7 VoLTE

api/yopen_volte.h 与 AMR 硬编解 api/yopen_voice.h

通话控制

yopen_vc_errcode_e yopen_voice_call_start (uint8_t nSim, char *dial_num);  /* 拨号 */
yopen_vc_errcode_e yopen_voice_call_answer(uint8_t nSim);                  /* 接听 */
yopen_vc_errcode_e yopen_voice_call_end   (uint8_t nSim);                  /* 挂断 */
yopen_vc_errcode_e yopen_voice_call_clcc  (uint8_t nSim, uint8_t *total,
                                           yopen_vc_info_s vc_info[YOPEN_VC_MAX_NUM]);
void yopen_voice_call_callback_register(yopen_vc_event_handler_t cb);
yopen_volte_errcode_e yopen_volte_ims_reg_get(uint8_t nSim, uint8_t *mode);

事件 yopen_vc_event_id_eINIT_OK_IND / RING_IND / CONNECT_IND / NOCARRIER_IND / IMSREG_IND / INCOMING_NUMBER_IND / SAMPLERATE_IND / ERROR_IND

语音数据流(20 ms 一帧)

int yopen_volte_voice_start(uint8_t codecType);   /* 0=AMR-NB(8k), 1=AMR-WB(16k) */
int yopen_volte_voice_stop(void);
int yopen_volte_voice_read(yopen_volte_downlink_frame_t *frame, uint32_t timeout_ms);
int yopen_volte_voice_write(const int16_t *pcm, uint16_t len, uint8_t *ref);
int yopen_volte_voice_type_get(uint8_t *type);

下行帧结构同时给出 PCM 与 AMR,应用可任选:

typedef struct yopen_volte_downlink_frame {
    const uint8_t *dl_pcm;      /* 下行 PCM,本地播放用 */
    uint16_t       dl_pcm_len;  /* 8K→320B,16K→640B */
    const uint8_t *dl_amr;      /* 下行 AMR 原始编码,可自行解码 */
    uint8_t        dl_amr_len;
    const uint8_t *ul_amr;      /* 上行 AMR(采集) */
    uint8_t        ul_amr_len;
    bool           call_end;    /* 通话结束标记,本帧数据无效 */
} yopen_volte_downlink_frame_t;

AMR 硬件编解码 api/yopen_voice.h(单实例,20 ms 帧同步接口):

yopen_voice_handle_t *yopen_voice_eng_create(const yopen_voice_config_t *config);
int yopen_voice_enc_frame(handle, const int16_t *pcm_in, const int16_t *ref,
                          uint8_t *amr_out, uint16_t *amr_out_len);
int yopen_voice_dec_frame(handle, const uint8_t *amr_in, uint16_t amr_len,
                          int16_t *pcm_out, uint16_t *pcm_len);
int yopen_voice_eng_destroy(handle);

AMR 帧格式为 [TOC 1 字节][payload N 字节],TOC = 0x04 | (frameType << 3)


3. CODEC 配置

3.1 支持的 CODEC 总表

CODEC 数据接口 采样率 录音 驱动文件
ES8311(兼容 SC2601) I2S(Slave,I2S 模式) 8k / 16k / 22.05k / 24k / 32k / 44.1k / 48k src/es8311.c
TM8211 I2S(PCM 模式,无 MCLK / 无 DIN) 跟随主机 I2S 无独立驱动
PWM PWM 输出 建议用8K或者16K src/pwm.c
SPI(SigmaDelta) SPI 建议用16K src/spicodec.c
GX8006 I2S(Slave,polarity 1) 16k src/gx8006.c
ES7111 I2S(I2S 模式,无 MCLK / 无 DIN) 跟随主机 I2S 无独立驱动

注意audevStartRecord() 对 TM8211 / ES7111 直接返回 FALSE(这两颗是 DAC-only,无 ADC 通路)。PWM / SPI 也不走 I2S RX,同样不支持录音。

3.2 CODEC 选型-宏的传递链路

audio_config.h 中的宏映射链(互斥,按顺序匹配第一个命中的):

优先级 触发宏 AUDIO_CODEC_TYPE
1 BOARD_Y459 / BOARD_AI_CORE_V1_0 / BOARD_AI_CORE_V1_2 / BOARD_A01 / CONFIG_AUDIO_CODEC_ES8311 AUDIO_CODEC_ES8311
2 BOARD_PWM / BOARD_W09_PWM / CONFIG_AUDIO_CODEC_PWM AUDIO_CODEC_PWM
3 BOARD_TM8211 AUDIO_CODEC_TM8211
4 BOARD_CJC4344 AUDIO_CODEC_CJC4344
5 BOARD_AI_CORE_V1_3 / BOARD_W09_EVB_V1_0 AUDIO_CODEC_GX8006
6 BOARD_QOSA AUDIO_CODEC_I2S
7 CONFIG_AUDIO_CODEC_SPI AUDIO_CODEC_SPIAUD
8 BOARD_W09_ES7111 AUDIO_CODEC_ES7111
都没定义 #error "AUDIO_CODEC_TYPE UNDEFINE"

使用示例(project/call.cmakeproject/audio_loop.cmake):

# 硬件板型:W09 EVB V2.0(ES8311 Codec)
set(BOARD_W09_EVB_V2_0 TRUE)
set(CONFIG_AUDIO_CODEC_ES8311 TRUE)

现状提醒BOARD_TM8211BOARD_W09_ES7111CONFIG_AUDIO_CODEC_SPIBOARD_CJC4344 目前没有任何现成工程 set 过,需要自行在 project cmake 中启用。其中 BOARD_CJC4344audio_config.c 的 switch 中尚无对应 case,属于未实现路径。

3.3 ES8311(兼容 SC2601)接口

image-20260827182107614

3.4 TM8211接口

image-20260827182107614

3.5 PWM 音频接口

image-20260827182107614

3.6 SPI 音频接口

image-20260827182107614

3.7 PA 与 CODEC 电源

两者都由 audio_config.h 中的开关宏控制,audio_device.c 负责时序。

#define BOARD_PA_SUPPORT               /* 定义即启用 PA */
#define YOPEN_PIN_PA                63
#define YOPEN_PIN_PA_FUNC           0
#define YOPEN_PIN_PA_GPIO           1

#define BOARD_CODECPOWER_SUPPORT       /* 定义即启用 codec 供电控制 */
#define YOPEN_PIN_CODECPOWER        66
#define YOPEN_PIN_CODECPOWER_FUNC   4
#define YOPEN_PIN_CODECPOWER_GPIO   18

全双工下的资源共享:audioDevSetPA接口。I2S 的打开由引用计数 s_i2s_open_flag 管理,播放与录音共享同一个 I2S 端口;只有播放和录音都停止后才会真正 yopen_i2s_close(),中途不会重复初始化 codec。


4. Demo:TTS 与播放器

4.1 demo_tts.c

TTS 合成 + 播放

工程:project/tts.cmake,编译命令 build.bat <bsp_module> tts

核心思路:TTS 引擎和播放器通过一个 osiPipe 解耦。

%%{init: {"fontFamily": "Microsoft YaHei, PingFang SC, Noto Sans CJK SC, sans-serif"}}%%

flowchart LR
    A["yopen_tts_start()<br/>文本 UTF-8"] --> B["TTS 引擎"]
    B -->|"_data_cb 回调"| C["osiPipeWriteAll()"]
    C --> D["osiPipe (32 KB)"]
    D -->|"pcm_play_read()"| E["auReader_t"]
    E --> F["auDecoderCreate(PCM)"]
    F --> G["auPlayerStart()"]
    G --> H["喇叭"]
    B -->|"_end_cb 回调"| I["合成结束通知"]

4.2 demo_player.c

HTTP 流式播放器

工程:project/player.cmake,编译命令 build.bat <bsp_module> player

需求中提到的 “demo_play.c” 即此文件;仓库中实际文件名为 demo_player.c

支持两种数据源,由文件头部宏切换:

#define PLAY_TYPE_BUF   0   // 1: 通过内置 BUF 播放;0: 通过 HTTP URL 播放

模式 0:HTTP 流式播放(默认)

demo_player.c 是一个典型的 HTTP 流式播放器:一边下载、一边解码、一边播放,数据在下载任务与解码任务之间用 osiPipe 做缓冲解耦。

%%{init: {"fontFamily": "Microsoft YaHei, PingFang SC, Noto Sans CJK SC, sans-serif"}}%%

flowchart TD
    A["player_task 启动"] --> B["yopen_aon_power_on()"]
    B --> C{"是否 GX8006 板型"}
    C -->|是| D["gx8006Init() + gx8006SetWakeup()"]
    C -->|否| E
    D --> E["轮询 yopen_nw_get_reg_status()<br/>直到注网成功(HTTP 依赖注网)"]
    E --> F["playerPlay()"]
    F --> G["auPlayerCreate()"]
    G --> H["按 URL 后缀选格式<br/>.mp3 → MP3,否则 OPUS"]
    H --> I["auDecoderCreate(&reader, format)"]
    I --> J{"OPUS?"}
    J -->|是| K["auDecoderSetParam()<br/>24kHz 单声道 S16"]
    J -->|否| L
    K --> L["auPlayerStart(player, decoder)"]
    L --> M["playerOpenFile() 回调<br/>(解码器首读触发)"]
    M --> N["osiPipeCreate(8KB)<br/>创建 playerReadTask 下载任务"]
    N --> O["playerReadFile() 回调<br/>从 pipe 读,剥 ID3 头"]
    O --> P["解码 → I2S/PWM/SPI 输出"]
    P --> Q["auPlayerWaitFinish()"]
    Q --> R["auPlayerDelete + auDecoderDelete"]
    R --> S["sleep 5s,循环 5 次"]

下载任务与播放任务的分工

任务 职责
playerReadTask(独立任务,4096 栈) httpConnecthttpSendRequest → 循环 httpRecvResponse,把 body 写进 dataPipe;收满 recvContentLengthosiPipeSetEof(true)
playerReadFile(player 解码任务回调) dataPipe 阻塞读;首次读若检测到 ID3 魔数则计算并跳过 ID3v2 头

当前支持的格式与扩展

流格式 现状 判定方式
AUSTREAM_FORMAT_MP3 ✅ 已支持 URL 含 .mp3
AUSTREAM_FORMAT_OPUS ✅ 已支持 URL 不含 .mp3(默认走 OPUS),需 auDecoderSetParam 设 24 kHz
AUSTREAM_FORMAT_PCM ➕ 可扩展 需自行按 URL / 服务器约定选格式,并设 sample_rate / channel_count
AUSTREAM_FORMAT_AMRNB ➕ 可扩展 需自行选格式,8 kHz(AMR-WB 走软解不支持,需 POC 通道)

5. Demo:全双工与打电话

5.1 全双工回环

demo_audio_loop.c

数据流:MIC → recorder(POC/降噪) → pipe → player(POC) → 喇叭

%%{init: {"fontFamily": "Microsoft YaHei, PingFang SC, Noto Sans CJK SC, sans-serif"}}%%

flowchart LR
    A["MIC"] --> B["auRecorderStartPipe<br/>AUDEV_RECORD_TYPE_POC<br/>AUSTREAM_FORMAT_AMRWB"]
    B -->|"CP 侧 降噪 + AMR 硬编"| C["osiPipe (16 KB)"]
    C --> D["duplex_play_read()<br/>auReader_t"]
    D --> E["auVoiceAmrDecoderCreate<br/>CP 硬解"]
    E --> F["auPlayerStartV2<br/>AUDEV_PLAY_TYPE_POC"]
    F --> G["喇叭"]
    C -.->|"osiPipeSetReaderCallback"| H["pcm_data_cb<br/>水位监控 / 丢旧数据"]
  1. 必须用 auPlayerStartV2auPlayerStart 与录音编码任务共用 audio_work_stack 静态栈,全双工下会冲突;auPlayerStartV2 用独立 malloc 堆栈。
  2. POC 通道要配 auVoiceAmrDecoderCreate,不是 auDecoderCreate

关键配置

#define DUPLEX_SAMPLE_RATE      16000
#define DUPLEX_CHANNELS         1
#define DUPLEX_PIPE_SIZE        (16 * 1024)
#define DUPLEX_FORMAT           AUSTREAM_FORMAT_AMRWB
#define DUPLEX_RECORD_TYPE      AUDEV_RECORD_TYPE_POC
#define DUPLEX_PLAY_TYPE        AUDEV_PLAY_TYPE_POC

5.2 VoLTE 打电话

demo_call.c

业务层完全不碰音频接口,只用 call.h 的 5 个函数:

int  call_init(const call_config_t *cfg, call_event_cb_t cb);   /* 初始化 + 注册事件回调 */
int  call_make(const char *number);                             /* 拨号(异步) */
int  call_answer(void);                                         /* 接听(异步) */
int  call_hangup(void);                                         /* 挂断/拒接(异步) */
int  call_deinit(void);                                         /* 反初始化,幂等 */
bool call_is_ims_ready(void);                                   /* 查询 IMS 是否就绪 */
int  call_set_ringtone(const uint8_t *pcm, uint32_t len, uint32_t sample_rate); /* 可选 */

事件 call_event_id_e

事件 含义 携带字段
CALL_EVT_IMS_READY IMS 已注册且支持 MMTEL voice
CALL_EVT_IMS_LOST IMS 去注册 / 失去语音能力
CALL_EVT_INCOMING 来电 evt->number
CALL_EVT_RINGING 去电拨打中(回铃音阶段)
CALL_EVT_CONNECTED 接通,双向语音已就绪
CALL_EVT_ENDED 结束 evt->reasonYOPEN_VC_REASON_*
CALL_EVT_ERROR 内部错误 evt->errorcall_err_e

事件回调运行在 call engine 任务上下文(非中断上下文),但仍建议只投递消息、不做耗时操作。

Demo 状态机

%%{init: {"fontFamily": "Microsoft YaHei, PingFang SC, Noto Sans CJK SC, sans-serif"}}%%

stateDiagram-v2
    [*] --> WAIT_INCOMING
    WAIT_INCOMING --> WAIT_ANSWER: EVT_INCOMING(记录号码)
    WAIT_ANSWER --> WAIT_CONNECT: power key → call_answer()
    WAIT_ANSWER --> WAIT_INCOMING: EVT_ENDED(对端取消)
    WAIT_CONNECT --> TALK: EVT_CONNECTED
    WAIT_CONNECT --> WAIT_INCOMING: EVT_ENDED / 30s 超时 → call_hangup()
    TALK --> WAIT_CALLBACK: 30s 到 / 对端挂断(来电场景)
    TALK --> WAIT_INCOMING: 30s 到 / 对端挂断(回拨场景)
    WAIT_CALLBACK --> WAIT_CONNECT: 5s 后 call_make(记录的号码)

下行泵call_downlink_pump):

while (!s_pump_exit) {
    if (yopen_volte_voice_read(&frame, 40) != 0) continue;
    if (frame.dl_pcm && frame.dl_pcm_len > 0) {
        osiPipeWriteAll(pipe, frame.dl_pcm, frame.dl_pcm_len, YOPEN_WAIT_FOREVER);
        /* 帧长 ≥640 判为 16k,否则 8k;与当前采样率不符则投消息重建下行 */
    }
}

上行泵call_uplink_pump):

while (!s_pump_exit) {
    osiPipeReadAll(pipe, buf, frame_20ms, YOPEN_WAIT_FOREVER);
    int16_t ref[320];
    int ref_len = auVoicePopRefData(ref, frame_20ms);          /* 取 AEC 参考 */
    yopen_volte_voice_write((const int16_t *)buf, frame_20ms,
                            ref_len > 0 ? (uint8_t *)ref : NULL);
}

codec 采样率自适应s_codec 默认 1(AMR-WB,16 kHz)。回铃音阶段用兜底值,接通后由 YOPEN_VC_SAMPLERATE_IND 事件或下行帧长检测自动纠正,必要时重建下行通路(call_downlink_rebuild)。

内部关键配置

配置
CALL_SIM 0
CALL_FORMAT AUSTREAM_FORMAT_PCM
CALL_PIPE_SIZE 160 KB
CALL_PUMP_STACK_SIZE 4096
CALL_PUMP_PRIORITY APP_PRIORITY_NORMAL
CALL_RINGBACK_CODEC 1(AMR-WB 16k)
内置振铃音 ring_16k.c,16 kHz 单声道 S16LE,循环播放

停止顺序call_teardown):先停录音(上行)再停播放(下行) —— 因为 audevStopRecord 依赖 I2S 全双工的 FRAME_END 回调。

6. 测试

6.1 ES8311+DEMO_TTS

  • 配置tts.cmake 选择硬件和CODEC类型
set(BOARD_W09_EVB_V2_0 TRUE)

set(CONFIG_AUDIO_CODEC_ES8311 TRUE)
  • 编译指令
.\build.bat YM310_W09C.A60 tts 

6.2 PWM+DEMO_TTS

  • 配置tts.cmake 选择硬件和CODEC类型
set(BOARD_W09_EVB_V2_0 TRUE)

set(CONFIG_AUDIO_CODEC_PWM TRUE)
  • 编译指令
 .\build.bat YM310_W09C.A60 tts 

6.3 SPI+DEMO_TTS

  • 配置tts.cmake 选择硬件和CODEC类型
set(BOARD_W09_EVB_V2_0 TRUE)

set(CONFIG_AUDIO_CODEC_SPI TRUE)
  • 编译指令
.\build.bat YM310_W09C.A60 tts 

6.4 ES8311+DEMO_PLAYER

  • 配置player.cmake 选择硬件和CODEC类型
set(BOARD_W09_EVB_V2_0 TRUE)

set(CONFIG_AUDIO_CODEC_ES8311 TRUE)
  • 编译指令
.\build.bat YM310_W09C.A60 player

附录:相关文件索引2

路径 说明
api/yopen_tts.h TTS 引擎接口
api/yopen_volte.h VoLTE 通话控制 + 语音上下行
api/yopen_voice.h AMR 硬件编解码
api/yopen_i2s.h / yopen_pwmaud.h / yopen_spiaud.h 音频硬件接口
components/audio/include/audio_config.h CODEC 类型选择与全部引脚配置
components/audio/include/audio_types.h 通道类型、流格式、帧结构等公共定义
components/audio/include/audio_player.h 播放器接口
components/audio/include/audio_recorder.h 录音器接口
components/audio/include/audio_decoder.h / audio_encoder.h 编解码器与 AEC 参考源
components/audio/include/audio_device.h 音量 / 静音 / MIC 增益 / PA 回调
components/audio/src/audio_device.c 设备抽象与 CODEC 分发
components/audio/src/es8311.c ES8311 驱动(含 SC2601 兼容)
components/audio/src/pwm.c / spicodec.c / gx8006.c PWM / SPI / GX8006 驱动
components/call/inc/call.h 通话组件对外接口
components/call/src/call.c 通话组件实现(上下行泵、状态机)
components/demo/demo_tts.c TTS Demo
components/demo/demo_player.c 播放器 Demo
components/demo/demo_audio_loop.c 全双工回环 Demo
components/demo/demo_call.c 打电话 Demo
components/demo/demo_record.c 录音 Demo
project/tts.cmake / player.cmake / audio_loop.cmake / call.cmake 对应工程配置