跳转至

07 通话中播放音频(Air780EHM支持)

作者:拓毅恒 | 最后修改:2026-07-08

一、通话中播放音频概述

  • 通话中播放外部音频:在 VoLTE 通话建立后,给对方播放音频文件或 TTS 文本的功能,可用于呼叫中心语音播报、通话中信息提示等场景。
  • 文件播放:支持播放 MP3、AMR 等格式的音频文件,框架自动从文件头解析采样率、位宽、声道数等参数。
  • TTS 播放:支持将文本转为语音播放,当前 V2046 固件 TTS 仅支持 16K 通话质量播放。
  • 通话模式:支持两种模式——呼入自动接听后播放(IS_DIAL=false)和主动拨出后播放(IS_DIAL=true)。

二、演示功能概述

本章节将演示如何使用 Air780EHM 实现在 VoLTE 通话中给对方播放外部音频:

  1. 呼入自动接听播放:来电响铃 2 声后自动接听,音频通道建立后播放预设音频
  2. 主动拨出播放:设备自动拨打预设号码,对方接通后播放预设音频
  3. 文件播放模式:根据通话质量(8K/16K)自动选择对应采样率的 MP3 文件播放
  4. TTS 播放模式:将文本转为语音在通话中播放给对方

三、准备硬件环境

参考:Air780EHM 硬件环境清单,准备好硬件环境。以下两种环境,任选一种即可。

3.1 Air780EHM 整机开发板

Air780EHM 开发板提供了丰富的音频接口资源,可通过开发板上的音频接口进行连接和测试。

具备 volte 功能的电话卡插入开发板/核心板的 sim 卡槽。

3.2 Air780EHM 核心板 + AirAUDIO_1010 配件板

Air780EHM 核心板集成了移动网络通信功能。

注意: 如果搭配 AirAUDIO_1010 扩展板测试,需将 AirAUDIO_1010 扩展板中 PA 开关拨到 OFF,让软件控制 PA,避免 pop 音

Air780EHM 核心板和 AirAUDIO_1010 配件板的硬件接线方式为:

Air780EHM核心板
AirAUDIO_1010配件板
26/I2S_MCLK
I2S_MCLK
30/I2S_BCK
I2S_BCK
31/I2S_LRCK
I2S_LRCK
32/I2S_DIN
I2S_DIN
33/I2S_DOUT
I2S_DOUT
67/I2C1_SCL
I2C_SCL
66/I2C1_SDA
I2C_SDA
25/GPIO26
PA_EN
23/GPIO2
8311_EN
VDD_EXT
VCC
GND
GND

Air780EHM 核心板购买链接:合宙 Air780EHM 核心板 内置 Audio Codec 支持 VoLTE/TTS-淘宝网

四、准备软件环境

4.1 工具 + 内核固件 + 脚本

1、烧录工具 Luatools

2、本 demo 仅支持 Air780EHM V2046 及以上版本的 13/113 号固件使用。https://docs.openluat.com/air780EHM/luatos/firmware/version/

3、 luatos 需要的脚本和资源文件

4、lib 脚本文件:使用 Luatools 烧录时,勾选 添加默认 lib 选项,使用默认 lib 脚本文件;

4.2 API 介绍

这里仅介绍本篇文档所使用的 API,详情请查看:API - cc

cc.extern_source(source, is_add_record, is_error_stop, codec_id, sample_rate, data_bits, channel_nums, is_signed)

通话中附加额外的音频数据播放,可在通话中向对方播放音频文件或 TTS 文本。

cc.quality()

获取当前通话质量(采样率),返回值:1=8K 低音质,2=16K 高音质。用于根据通话质量选择对应采样率的音频文件。

cc.on(event, callback)

注册通话事件回调函数。

cc.lastNum()

获取来电号码。

cc.accept(id)

接听来电。

cc.dial(id, number)

拨打电话。

五、代码演示

5.1 通话播放外部音频应用

--[[
@module  play_audio_during_cc
@summary 通话中播放音频(文件/TTS)
@version 1.0
@date    2026.07.08
@author  拓毅恒
@usage
本模块演示在VoLTE通话中给对方播放音频文件或TTS文本。
通话接通后在AUDIO_START事件中自动触发播放。

工作流程:
- 系统启动后初始化音频设备和CC
- 由宏IS_DIAL决定是主动拨号还是等待来电接听
- 通话建立(AUDIO_START)后,播放预设音频

注意事项:
1. 本demo使用新音频框架,固件需要V2046及以上的13/113号固件才能播放
2. 使用本demo时需要确认audio_drv.lua中初始化配置的音频框架audio_mode = "new" 才能播放
3. 传入音频文件时(如{"/luadb/test_16k.mp3"}),框架自动从文件头解析
   采样率、位宽、声道数等参数,并与当前通话参数自动校验
4. 校验不匹配会静默失败
5. 准备音频文件时需注意:
   - 确保文件是16bit、单声道的MP3文件
   - 最好准备两种采样率的文件(8K和16K各一份),运行时通过cc.quality()判断后选择
6. 当前V2046固件 TTS 仅支持16K通话质量播放,8K时会静默失败

常用cc.extern_source调用示例:
1. 播放文件:   cc.extern_source({"/luadb/test_16k.mp3"})       -- 框架自动识别
2. 播放TTS:   cc.extern_source("你好,测试一下")                
3. 裸PCM数据: cc.extern_source(pcm_zbuff, nil, audio_v2.DATA_CODEC_TYPE_RAW, 8000, 16, 1, true)
4. 停止播放:  cc.extern_source(nil)
]]

-- 引入音频设备模块
local exaudio = require "exaudio"
local audio_drv = require "audio_drv"

-- ====================== 配置区域 ======================

-- 通话模式选择(二选一):
-- true = 主动拨出,false = 等待来电自动接听
local IS_DIAL = false
-- 主动拨出时的目标号码(IS_DIAL=true时有效)
local DIAL_NUMBER = "10000"

-- 播放模式选择(二选一):
local PLAY_MODE = "FILE"         -- "FILE"播放文件, "TTS"播放文本
-- 文件播放模式(PLAY_MODE="FILE"时生效):
local PLAY_FILE_8K = "/luadb/test_8k.mp3"     -- 8K通话时播放的文件
local PLAY_FILE_16K = "/luadb/test_16k.mp3"   -- 16K通话时播放的文件
-- TTS播放模式(PLAY_MODE="TTS"时生效):
local PLAY_TTS_TEXT = "你好,测试一下,123456789"

-- ====================== 全局状态变量 ======================
local call_counter = 0                 -- 响铃计数器
local caller_number = ""               -- 来电号码
local is_connected = false             -- 通话连接状态

-- ====================== 通话事件处理 ======================
-- 来电自动接听模式
local function handle_incoming_call(status)
    if status == "INCOMINGCALL" then
        -- 获取来电号码
        caller_number = cc.lastNum() or "未知号码"
        call_counter = call_counter + 1
        log.info("play_audio_during_cc", "收到来电,号码:", caller_number, "响铃次数:", call_counter)
        -- 响铃2声后自动接听
        if call_counter >= 2 then
            log.info("play_audio_during_cc", "自动接听来电")
            cc.accept(0)
            call_counter = 0
        end
    end
end

-- 主动拨号模式
local function handle_outgoing_call(status)
    if status == "MAKE_CALL_OK" then
        log.info("play_audio_during_cc", "拨号请求成功")
    elseif status == "CONNECTED" then
        log.info("play_audio_during_cc", "电话已接通")
    elseif status == "MAKE_CALL_FAILED" then
        log.info("play_audio_during_cc", "拨号失败")
    end
end

-- 根据模式和通话质量获取播放源
local function get_play_source_by_quality(cc_quality)
    if PLAY_MODE == "TTS" then
        return PLAY_TTS_TEXT
    end
    -- 文件模式:根据通话质量选择对应的音频文件
    if cc_quality == 1 then
        return {PLAY_FILE_8K}
    elseif cc_quality == 2 then
        return {PLAY_FILE_16K}
    end
    return {PLAY_FILE_16K}
end

-- 通话中播放外部音频(接通音频通道后触发)
local function play_extern_source()
    local quality = cc.quality()
    log.info("play_audio_during_cc", "通话质量:", quality, "(1=8K低音质, 2=16K高音质)")

    -- 先清理上一次通话可能残留的extern_source状态,确保第二次也能播放
    cc.extern_source()
    -- 根据模式获取播放源
    local play_source = get_play_source_by_quality(quality)
    -- 播放音频
    local ok = cc.extern_source(play_source)
    if ok then
        if type(play_source) == "table" then
            log.info("play_audio_during_cc", "开始播放文件:", play_source[1])
        elseif type(play_source) == "string" then
            log.info("play_audio_during_cc", "开始播放TTS:", play_source)
        end
    else
        log.error("play_audio_during_cc", "播放失败,可能音频参数与通话不匹配")
    end
end

-- 主动拨号模式的定时拨号处理
local function handle_dial_timer()
    log.info("play_audio_during_cc", "开始拨号:", DIAL_NUMBER)
    cc.dial(0, DIAL_NUMBER)
end

-- CC_IND事件处理器
local function handle_cc_ind(status)
    log.info("CC状态", status)

    -- 根据模式分发事件
    if IS_DIAL then
        handle_outgoing_call(status)
    else
        handle_incoming_call(status)
    end

    -- AUDIO_START:音频通道已建立,可以播放外部音频
    if status == "AUDIO_START" then
        play_extern_source()
    end

    -- SPEECH_START:通话开始
    if status == "SPEECH_START" then
        is_connected = true
        log.info("play_audio_during_cc", "通话已建立")
    end

    -- 需要处理的通用状态
    if status == "READY" then
        sys.publish("CC_READY")  -- 发布系统就绪事件
        -- 主动拨号模式:电话系统就绪后自动拨号
        if IS_DIAL then
            sys.timerStart(handle_dial_timer, 1000)
        end
    elseif status == "HANGUP_CALL_DONE" or status == "MAKE_CALL_FAILED" or status == "DISCONNECTED" then
        -- 通话结束,关闭PA节省功耗,保留driver下次通话时快速恢复
        exaudio.pm(audio.SHUTDOWN)
        is_connected = false
        call_counter = 0
        log.info("play_audio_during_cc", "通话结束")
    end
end

-- ====================== 主事件处理器 ======================
sys.subscribe("CC_IND", handle_cc_ind)

-- ====================== 电话系统初始化 ======================
local function init_cc()
    -- 初始化音频设备
    audio_drv.initAudioDevice()

    -- 等待电话系统就绪
    sys.waitUntil("CC_READY")

    -- 初始化电话功能
    cc.init(audio_drv.getMultimediaId())

    log.info("play_audio_during_cc", IS_DIAL and "主动拨号模式" or "来电接听模式")
    log.info("play_audio_during_cc", "播放模式: ", PLAY_MODE)
    log.info("play_audio_during_cc", "电话系统初始化完成")
end

-- 启动初始化任务
sys.taskInit(init_cc)

log.info("play_audio_during_cc", "通话外部音频播放模块加载完成")

六、功能演示

6.1 选择功能

首先注释掉 main.lua 中默认通话功能模块,打开通话中播放外部音频功能模块:

6.2 配置播放参数

audio_drv.lua 文件中,将音频框架改为 new

6.3 烧录代码

使用 Luatools 将代码烧录到 Air780EHM 开发板或核心板中。

6.4 电话系统初始化完成

烧录完毕后,系统会自动初始化音频设备和电话系统。日志中会显示模块加载和初始化完成信息。

6.5 来电响铃(呼入模式)

当有来电时,日志会显示来电号码和响铃次数。响铃 2 声后自动接听。

6.5.1 启动拨号(主动拨出模式)

如果在开头设置为 IS_DIAL = true,设备将自动拨打预设号码,日志显示拨号请求。

6.7 通话建立并播放音频

对方接通后,显示 SPEECH_STARTAUDIO_START 状态,自动触发播放外部音频。

6.8 播放完成

音频播放完成后,显示 EXT_SRC_DONE 事件。通话继续,等待对方挂断。

6.9 通话结束

对方挂断后,通话结束,显示 DISCONNECTED 状态,系统自动关闭 PA 节省功耗。

七、总结

至此,我们演示了使用 Air780EHM 在 VoLTE 通话中播放外部音频的全过程:

  1. 呼入自动播放:来电响铃 2 声后自动接听,音频通道建立后自动播放预设音频
  2. 主动拨出播放:设备自动拨打预设号码,对方接通后播放预设音频
  3. 文件播放模式:根据通话质量自动选择对应采样率的音频文件,支持 MP3/AMR 格式
  4. TTS 播放模式:将文本转为语音在通话中播放给对方

注意事项

  • 本 demo 使用新音频框架,需确认 audio_drv.luaaudio_mode = "new"
  • 固件需要 V2046 及以上的 13/113 号固件才能播放
  • 音频文件准备好 8K 和 16K 两种采样率,运行时通过 cc.quality() 判断后选择
  • 当前 V2046 固件 TTS 仅支持 16K 通话质量播放,8K 时会静默失败
问一下 AI