音频使用说明¶
本文主要介绍音频的开发和使用:包含总体架构、核心接口说明、CODEC 配置、Demo:TTS 与播放器、Demo:全双工与打电话
目录¶
1. 总体架构¶
1.1 分层结构¶
依赖方向自上而下:应用层 → 业务组件 / 语音合成 → 音频框架 → CODEC 驱动 → api 硬件层。
- 应用层:
demo_tts.c、demo_player.c、demo_audio_loop.c、demo_call.c - 业务组件 / 语音合成:
components/call(VoLTE 通话封装)api/yopen_tts.h(TTS 引擎,输出 PCM)
- 音频框架:
audio_player、audio_recorder、audio_decoder、audio_encoderaudio_device.c(设备抽象)⇄audio_config.c(引脚 / CODEC 参数表)
- CODEC 驱动:
es8311.c(I2C)、gx8006.c(UART)、pwm.c、spicodec.c、纯 I2S(TM8211 / ES7111) - api 硬件接口层:
yopen_i2s、yopen_i2c、yopen_pwmaud、yopen_spiaud、yopen_volte、yopen_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 可用 */
auPlayerStart 与 auPlayerStartV2 的差异(重要)
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_e:INIT_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.cmake、project/audio_loop.cmake):
# 硬件板型:W09 EVB V2.0(ES8311 Codec)
set(BOARD_W09_EVB_V2_0 TRUE)
set(CONFIG_AUDIO_CODEC_ES8311 TRUE)
现状提醒:
BOARD_TM8211、BOARD_W09_ES7111、CONFIG_AUDIO_CODEC_SPI、BOARD_CJC4344目前没有任何现成工程set过,需要自行在 project cmake 中启用。其中BOARD_CJC4344在audio_config.c的 switch 中尚无对应 case,属于未实现路径。
3.3 ES8311(兼容 SC2601)接口¶

3.4 TM8211接口¶

3.5 PWM 音频接口¶

3.6 SPI 音频接口¶

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 栈) |
httpConnect → httpSendRequest → 循环 httpRecvResponse,把 body 写进 dataPipe;收满 recvContentLength 后 osiPipeSetEof(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/>水位监控 / 丢旧数据"]
- 必须用
auPlayerStartV2。auPlayerStart与录音编码任务共用audio_work_stack静态栈,全双工下会冲突;auPlayerStartV2用独立malloc堆栈。 - 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->reason(YOPEN_VC_REASON_*) |
CALL_EVT_ERROR |
内部错误 | evt->error(call_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 |
对应工程配置 |