跳转至

voip 语音通话(SIP/RTP 媒体)

一、概述

voip 是合宙 LuatOS 的 SIP 通话媒体库,负责把语音按 RTP 打包收发、编解码、抖动缓冲(jitter buffer)与回声消除(AEC)。它只做“媒体面”,信令面(REGISTER/INVITE/SDP 协商)由 SIP 协议栈完成,两者配合才是一通完整的 SIP 电话。

要点:

1、模块名为 voip。典型流程是 voip.start(opts) 建链 → voip.on("state", cb) 观察状态 → voip.stop() 挂断;参数里的对端地址与端口必须来自 SDP 协商结果,不能自己编;

2、voip 与 cc 是两个不同的库:cc 面向运营商 VoLTE 通话(走蜂窝语音通道),voip 面向纯 IP 的 SIP/RTP 通话(走 socket),同型号模组上两者可以并存但用途不同;

3、音频有两种工作模式:voip.AUDIO_MODE_I2S(默认,库直接驱动 codec/I2S 硬件)与 voip.AUDIO_MODE_BRIDGE(纯 PCM 桥接,库里不碰 codec,由脚本用 pcmIn/pcmOut 自己搬数据)。后者用于没有 codec 芯片、或要把语音接到别处(如 CC 桥接)的场景;

4、通话中的播报(TTS/文件)、录音、DTMF、SRTP 加密都是可裁剪的子功能,各自有编译开关,固件没开时对应函数直接是 nil。

⚠️ 该库需要固件在编译时打开 VoIP 组件(源码标记 LUAT_USE_VOIP)。子功能开关分别是 LUAT_USE_VOIP_PLAY(playTTS/playFile)、LUAT_USE_VOIP_RECORD(record)、LUAT_USE_VOIP_BRIDGE(pcmIn/pcmOut/pcmResampler/setAudioMode/bridgeTone/bridgeMute)、LUAT_USE_SRTP(security)。脚本里 voip 为 nil 表示整库缺失,voip.playTTS 为 nil 表示只缺子功能,两种情况都要找对接 FAE 确认固件版本。

⚠️ voip.G729、voip.G722 也是条件导出的常量:固件没编入对应编解码器时它们是 nil,此时 voip.start 里传 2 或 3 会失败,判断方法就是先看常量是否存在。

二、核心示例

-- 前提:SIP 信令已完成 SDP 协商,拿到对端 IP、端口、编解码与 ptime
voip.on("state", function(state)
    log.info("voip", "state", state)          -- starting / running / stopping / idle
end)

voip.on("error", function(err)
    log.error("voip", "error", err)
end)

-- 每 5 秒回调一次统计,便于观察丢包与抖动
voip.on("stats", function(s)
    log.info("voip", "tx", s.tx_packets, "rx", s.rx_packets, "lost", s.rx_lost)
end)

local ok = voip.start({
    remote_ip   = "192.168.1.100",   -- SDP 里的对端地址
    remote_port = 10000,             -- SDP 里的对端端口
    local_port  = 10000,             -- 本端端口,必须与 SDP 里通告的一致
    transport   = "udp",
    codec       = voip.PCMU,         -- 与协商结果一致
    ptime       = 20,
    jitter_depth = 3,
    aec         = true,
    aec_mode    = "speex",
    stats_interval = 5000,
})
if not ok then
    log.error("voip", "start failed")
    return
end

-- 通话中按键:先取当前会话号,避免旧按键串到下一通电话
local session = voip.getSession()
if session then
    voip.sendDtmf("2", 160, session)
end

-- 挂断
voip.stop()

桥接模式(无 codec 芯片、自己做采集播放)的写法:

voip.setAudioMode(voip.AUDIO_MODE_BRIDGE)
voip.start({ remote_ip = "192.168.1.100", remote_port = 10000,
             local_port = 10000, transport = "udp", codec = voip.PCMU })

sys.taskInit(function()
    while voip.isRunning() do
        local pcm = voip.pcmOut(160)          -- 取出下行 8kHz PCM16
        if pcm then
            audio_play(pcm)                   -- 业务侧送到喇叭
        end
        sys.wait(20)
    end
end)

三、常量详解

3.1 编解码常量

常量 值 含义
voip.PCMU 0 G.711 μ-law(PCMU,payload type 0),8kHz 窄带,默认编码
voip.PCMA 1 G.711 A-law(PCMA,payload type 8),8kHz 窄带
voip.G729 2 G.729A,仅 8kHz,ptime 必须是 10ms 的整数倍;需固件启用 LUAT_SUPPORT_G729
voip.G722 3 G.722,仅 16kHz 宽带 64kbps,ptime 必须是 20ms 的整数倍,启用后 AEC 自动关闭;需固件启用 LUAT_SUPPORT_G722

注意事项:

1、voip.G729 / voip.G722 未编入固件时是 nil,用之前要判断,不要直接传数字 2/3;

2、G.722 是宽带编码,采样率必须是 16kHz;把 sample_rate 留成默认的 8000 会和协商结果对不上,对端听到的是变调的声音;

3、编解码必须与 SDP 协商结果一致,不能本端随便挑一个更省带宽的。

3.2 传输方式常量

常量 值 含义
voip.TRANSPORT_UDP 0 默认,RTP over UDP
voip.TRANSPORT_TCP 1 RTP over TCP,本端主动连对端
voip.TRANSPORT_TCP_PASSIVE 2 RTP over TCP,本端监听,等平台连进来

TCP 承载时 RTP 按 RFC 4571 做长度定界,与纯 UDP 的报文边界不同,所以 transport 必须与 SDP 里 m=audio ... TCP/RTP/AVP 的写法一致,否则对端解不出包。

3.3 音频模式常量

常量 值 含义
voip.AUDIO_MODE_I2S 0 默认,库直接驱动音频硬件(codec/I2S),脚本不用管 PCM
voip.AUDIO_MODE_BRIDGE 1 纯 PCM 桥接,库不碰 codec,由脚本用 pcmIn/pcmOut 搬数据;需 LUAT_USE_VOIP_BRIDGE

3.4 版本探测常量

常量 值 含义
voip.DTMF_VERSION 1 RTP DTMF(RFC 4733)接口版本号,用于探测固件是否支持 sendDtmf 与 dtmf 事件;老固件里该常量为 nil

四、函数详解

4.1 voip.start(opts)

功能

启动一路 SIP/RTP 媒体会话:绑定本端端口、连上对端、按协商参数开始收发语音。

参数

opts

参数含义:通话配置表
数据类型:table
取值范围:见下表字段
是否必选:是
注意事项:不是 table 时直接返回 false 并打印错误日志,不会抛 Lua 错误。

opts 各字段:

字段 类型 默认值 说明
remote_ip string 无 对端 IP,来自 SDP 的 c= 行。tcp_passive 时可省略
remote_port number 0 对端 RTP 端口,来自 SDP 的 m= 行。tcp_passive 时可省略
local_port number 0 本端端口,必须与 SDP 通告一致;tcp_passive 时必填
transport string/number "udp" "udp" / "tcp"(主动连)/ "tcp_passive"(监听),也可直接传 3.2 节的传输方式常量值
codec number voip.PCMU 编码方式,取 3.1 中的常量
ptime number 20 打包时长(毫秒)。G729 须为 10 的倍数,G722 须为 20 的倍数
ssrc number 0 发送端 SSRC,0 表示随机生成;GB28181 的 SDP y= 行要用它
sample_rate number 8000 采样率,窄带 8000、G722 用 16000
jitter_depth number 3 抖动缓冲深度(包数),越大越抗抖动但延迟越高
multimedia_id number 0 多媒体通道号,与 audio/codec 侧的通道对应
stats_interval number 5000 统计回调间隔(毫秒),0 表示不回调
aec boolean false 是否开启回声消除
aec_mode string "speex" "speex" 或 "bk"(黑盒模式),写错直接返回 false
aec_denoise boolean 跟随 aec 是否开降噪;不填时等于 aec 的取值
aec_agc boolean 见说明 自动增益。不填时:带 V2 DAC 同步的平台上为关,其它平台等于 aec_denoise
aec_delay_samples number 160 参考信号与麦克风的延迟对齐样本数
aec_tail number 120 回声拖尾长度(毫秒),房间越大要越大
tx_mute boolean false 只收不发(喊话/广播场景)
dtmf_rx_payload_type number 0 接收 DTMF 的 payload type,0 表示未协商、关闭 RTP DTMF 接收;范围 0~127
dtmf_tx_payload_type number 0 发送 DTMF 的 payload type,0 表示关闭;范围 0~127
dtmf_clock_rate number 8000 DTMF 的时钟频率,范围 0~8000
dtmf_events number 65535 DTMF 事件位图,范围 0~65535
adapter number 默认网卡 指定走哪块网卡(多网卡/多链路时用)
security userdata 无 voip.securitySetRemote 返回的句柄,启用 SRTP;固件未开 SRTP 时传了会返回 media_security_unsupported

四个 DTMF 字段必须与 SDP 协商结果一致:不填就不会收发 RTP DTMF,此时 voip.sendDtmf 会返回 dtmf_not_negotiated。

返回值

含义说明:是否成功启动媒体会话
数据类型:boolean
取值范围:true 成功;false 失败
注意事项:多数参数校验失败只返回 false 并在日志里打印原因;DTMF 参数越界与 SRTP 句柄非法这两种情况会额外返回第二个值("invalid_dtmf_negotiation" / "invalid_media_security" / "media_security_unsupported")。
返回示例:true

注意事项

1、非 tcp_passive 模式下 remote_ip 与 remote_port 必填,缺一个就返回 false;

2、tcp_passive 必须给 local_port,因为对端是平台侧主动连进来的,本端只需要监听端口;

3、返回 true 只代表媒体会话已开始工作,不代表对端已经收到声音——对端是否真的在发流,要看 voip.stats() 里的 rx_packets 是否增长。

示例

local ok = voip.start({
    remote_ip = "192.168.1.100", remote_port = 10000, local_port = 10000,
    transport = "udp", codec = voip.PCMA, ptime = 20,
    aec = true, aec_mode = "speex",
})

4.2 voip.stop()

功能

停止当前媒体会话,释放 RTP 端口与音频资源。

参数

无。

返回值

含义说明:是否停止成功
数据类型:boolean
取值范围:true 成功;false 当前没有会话或内部状态不允许
返回示例:true

注意事项

1、停止是异步收尾的,调用后要等 voip.on("state") 收到 "stopped"(或 getState() 变成 "idle")再启动下一通,否则可能因为端口还没释放而启动失败;

2、bridgeMute 的静音状态在会话启停时会自动恢复为未静音,不用手工复位。

示例

voip.stop()

4.3 voip.on(event, callback)

功能

注册 voip 事件回调。同一个事件重复注册会覆盖旧回调,传 nil 表示取消注册。

参数

event

参数含义:事件名
数据类型:string
取值范围:"state"、"stats"、"error"、"record"、"play"、"dtmf"
是否必选:是
注意事项:写错的事件名只打印一条警告并忽略,不会报错,容易误以为“没生效”。

callback

参数含义:回调函数
数据类型:function 或 nil
取值范围:任意
是否必选:是
注意事项:不是函数也不是 nil 时会抛参数错误。

各事件的回调参数:

事件 回调参数 说明
state state(string) 会话状态,取值为 starting/running/stopping/idle/error 等
stats stats(table) 统计表,字段见 4.7;按 stats_interval 周期回调
error err(string) 内部错误原因,多用于排障
play (event, info) 两个参数 event 为 started/done/stopped/error;info 见 4.12
record (event, info) 两个参数 录音状态变化,info 字段同 4.15
dtmf event(table) 一个表,含 event(rx/rx_end/tx_done/tx_failed)、digit、session、id、duration_ms、complete

⚠️ play 与 record 的回调是两个参数 (event, info),而 state/stats/error 只有一个参数。写成单参数的函数去接 play 事件时,第二个参数会丢失,这是最容易踩的坑。

返回值

无。

示例

voip.on("play", function(event, info)
    log.info("voipplay", event, info.id, info.state)
end)

voip.on("dtmf", function(e)
    if e.event == "rx" then
        log.info("dtmf", "收到按键", e.digit)
    end
end)

4.4 voip.getState()

功能

读取当前会话状态。

参数

无。

返回值

含义说明:会话状态
数据类型:string
取值范围:idle(空闲)/ starting(正在建链)/ running(通话中)/ stopping(正在停止)/ error(出错)/ unknown
返回示例:"running"

示例

log.info("voip", voip.getState())

4.5 voip.isRunning()

功能

判断媒体会话是否处于运行中,常用于循环里的退出条件。

参数

无。

返回值

含义说明:是否正在运行
数据类型:boolean
取值范围:true/false
返回示例:true

示例

while voip.isRunning() do
    sys.wait(100)
end

4.6 voip.getSession()

功能

取当前媒体会话编号。作用是给按键、播报这类异步操作带上“会话归属”,避免上一通电话的残留操作落到下一通上。

参数

无。

返回值

含义说明:当前媒体会话编号
数据类型:number 或 nil
取值范围:有会话时返回正整数;没有会话返回 nil
注意事项:编号在每次 start 后变化,不要缓存跨通话使用。
返回示例:1

示例

local session = voip.getSession()
if session then
    voip.sendDtmf("1", 160, session)
end

4.7 voip.stats()

功能

读取累计统计,用于判断链路质量(丢包、乱序、抖动、编解码耗时、AEC 状态)。

参数

无。

返回值

含义说明:统计表
数据类型:table
取值范围:字段见下表,全部为累计值(从本次 start 起算)
返回示例:{tx_packets = 1024, rx_packets = 1018, rx_lost = 6, ...}

主要字段分四组:

分组 字段 含义
发送 tx_packets / tx_bytes / tx_errors 已发 RTP 包数、字节数、发送失败次数
接收 rx_packets / rx_bytes / rx_lost / rx_out_of_order 收到包数、字节数、按序列号推算的丢包数、乱序包数
接收异常 rx_parse_fail / rx_bad_payload / rx_security_fail / rx_queue_dropped / rx_frame_dropped / rx_decode_dropped / rx_concealed / rx_decoder_resets 解析失败、载荷非法、SRTP 校验失败、队列丢包、整帧丢弃、解码丢弃、丢包补偿次数、解码器重置次数
编解码与音频 encode_frames / encode_ms / encode_max_ms / decode_frames / decode_ms / decode_max_ms / jb_played / jb_silence / audio_tx_commit_fail / audio_tx_underrun / audio_rx_dropped / audio_rx_samples / audio_render_samples / audio_normalized_samples / aec_mode 编解码帧数与耗时(含峰值)、抖动缓冲播出与补静音次数、音频提交失败/欠载/丢弃、采样统计、当前 AEC 模式

注意事项

1、rx_lost 是按 RTP 序列号推算的,不是网络层统计,参考价值在于“有没有持续增长”;

2、排查“对方听不到我”时先看 tx_packets 有没有涨,排查“我听不到对方”看 rx_packets;两个都涨但没声音,问题在音频通路(codec/模式)而不是网络;

3、encode_max_ms / decode_max_ms 突然变大,通常是主循环被别的任务长时间占用。

示例

local s = voip.stats()
log.info("voip", "tx", s.tx_packets, "rx", s.rx_packets, "lost", s.rx_lost, "aec", s.aec_mode)

4.8 voip.sendDtmf(digit, duration_ms, expected_session)

功能

在通话中异步发送一个 RFC 4733 网络按键,不影响音频收发。用于拨分机号、IVR 菜单选择。

参数

digit

参数含义:要发送的按键
数据类型:string
取值范围:单个字符,0-9、*、#、A-D
是否必选:是
注意事项:长度必须为 1,传整串号码直接失败。

duration_ms

参数含义:按键持续时间
数据类型:number
取值范围:100~1000 毫秒,默认 160
是否必选:否
注意事项:太短对端可能识别不到,太长会拖慢后续按键。

expected_session

参数含义:期望的媒体会话号
数据类型:number
取值范围:voip.getSession() 的返回值
是否必选:是
注意事项:与实际会话不一致时返回 stale_media_session,这是防止旧按键串到下一通电话的保护。

返回值

含义说明:是否受理 + 请求编号或失败原因
数据类型:boolean + number/string
取值范围:受理返回 true 与请求编号 id;失败返回 false 与原因
失败原因:stale_media_session(会话已变)/ dtmf_not_negotiated(SDP 未协商 DTMF)/ invalid_dtmf(按键或时长非法)/ dtmf_busy(上一次还没发完)/ dtmf_queue_failed(入队失败)
返回示例:true, 3

注意事项

1、是否真的发出去了要看 voip.on("dtmf") 的 tx_done;tx_done 只表示本端网络发送完成,不代表对端 IVR 已经处理;

2、接收侧每按一次键只上报一次 rx,rx_end 带结束包时长,不要拿 rx_end 再转发一遍同一个按键;

3、必须先让 SDP 协商出 DTMF payload type(dtmf_rx_payload_type/dtmf_tx_payload_type 非 0),否则本接口恒返回 dtmf_not_negotiated。

示例

local ok, id = voip.sendDtmf("2", 160, voip.getSession())
if not ok then
    log.warn("voip", "dtmf failed", id)
end

4.9 voip.playTTS(text, opts)

功能

把一段文本转成语音,直接播给通话对端,不占用本机音频设备。

参数

text

参数含义:要朗读的文本
数据类型:string
取值范围:UTF-8 文本,不能含 \0
是否必选:是
注意事项:TTS 只支持中文(依赖固件是否带 TTS 资源)。

opts

参数含义:播报配置
数据类型:table,可省略
取值范围:target="sip"/"cc"/"both"(默认 "sip",cc/both 仅纯 PCM 桥接可用);mode="replace"/"mix"(默认 "replace");gain(总增益,0~1);speech_gain(语音增益,0~1)
是否必选:否
注意事项:值越界或枚举写错会直接失败并返回 invalid_options。

返回值

含义说明:播放 ID 或失败原因
数据类型:number 或 nil + string
取值范围:成功返回播放 ID(正整数);失败返回 nil 与原因
失败原因:invalid_options(参数非法)/ busy(同一时刻只允许一个播报任务)/ 其它资源类原因
返回示例:1

注意事项

1、同一时刻只能有一个播报任务,busy 时要等 play 事件的 done 或 playStatus().state 变回 idle 再发下一段;

2、mode="replace" 会用播报替换原来的通话语音,"mix" 是叠加在通话语音上,广播/提示音场景一般用 mix;

3、gain 与 speech_gain 都是 0~1 的浮点,不是分贝值。

示例

local id, err = voip.playTTS("您好,请稍候", { target = "sip", mode = "replace", gain = 0.8 })

4.10 voip.playFile(path, opts)

功能

把本地音频文件播给通话对端,支持 PCM16 裸流、PCM WAV 与 MP3。

参数

path

参数含义:文件路径
数据类型:string
取值范围:文件系统里存在的路径,如 /luadb/prompt.mp3
是否必选:是
注意事项:裸 PCM 必须是 16bit 小端单声道,采样率默认 8000。

opts

参数含义:播报配置,含义同 4.9
数据类型:table,可省略
取值范围:在 4.9 的基础上,裸 PCM 还支持 sample_rate(8000~48000)与 channels(1 或 2)
是否必选:否
注意事项:WAV/MP3 的采样率由文件头决定,不要再用 sample_rate 覆盖。

返回值

含义说明:播放 ID 或失败原因
数据类型:number 或 nil + string
取值范围:成功返回播放 ID;失败返回 nil 与原因(invalid_options/busy/文件不存在等)
返回示例:2

示例

local id, err = voip.playFile("/luadb/prompt.mp3", { target = "both", mode = "mix" })

4.11 voip.playStop(id)

功能

异步停止指定的播报任务。

参数

id

参数含义:playTTS/playFile 返回的播放 ID
数据类型:number
取值范围:大于 0 且不超过 32 位无符号范围
是否必选:是
注意事项:传 0 或负数返回 false 与 invalid。

返回值

含义说明:是否接受停止请求
数据类型:boolean + string
取值范围:接受返回 true;不接受返回 false 与原因
返回示例:true

注意事项

停止是异步的:playStatus().state 变成 idle、或收到 play 事件的 stopped/done,才表示资源已释放、可以再次播报。

示例

voip.playStop(id)

4.12 voip.playStatus()

功能

查询当前播报状态。

参数

无。

返回值

含义说明:播报状态表
数据类型:table
取值范围:字段见下
返回示例:{id = 1, session = 1, state = "playing", target = "sip", mode = "replace", sip_samples = 8000, cc_samples = 0, reason = nil}
字段 含义
id 播放 ID,空闲时为 0
session 所属媒体会话号
state idle / loading / playing / stopping
target sip / cc / both
mode replace / mix
sip_samples 已提交到 SIP 发送接口的 8kHz 源样本数
cc_samples 已提交到 CC 发送接口的 8kHz 源样本数
reason 失败/结束原因,正常时为 nil

⚠️ sip_samples / cc_samples 是“已提交给发送接口”的样本数,不是对端已播放的进度,不能拿它做精确的播放进度条。

4.13 voip.recordStart(path, opts)

功能

开始本地双声道 WAV 录音(本端麦克风 + 对端下行各一路)。

参数

path

参数含义:录音文件路径
数据类型:string
取值范围:建议以 .wav 结尾
是否必选:是
注意事项:路径非法返回 invalid_path。

opts

参数含义:录音配置
数据类型:table,可省略
取值范围:max_seconds 最长录音秒数,默认 7200
是否必选:否

返回值

含义说明:是否开始成功
数据类型:boolean + string/nil
取值范围:成功 true 与 nil;失败 false 与原因
失败原因:invalid_path / invalid_state(当前状态不允许,如没有通话)/ already_recording / no_memory
返回示例:true, nil

注意事项

录音依赖 LUAT_USE_VOIP_RECORD;同一时刻只能有一路录音,重复调用返回 already_recording。空间不足时会丢帧,可通过 recordStatus().dropped_frames 观察。

示例

voip.recordStart("/luadb/call.wav", { max_seconds = 600 })

4.14 voip.recordStop()

功能

异步停止并收尾当前录音(写完 WAV 头)。

参数

无。

返回值

含义说明:是否发出停止请求
数据类型:boolean + string/nil
取值范围:成功 true 与 nil;失败 false 与 "invalid_state"
返回示例:true, nil

注意事项

返回 true 只代表请求已受理,要等 recordStatus().state 变成 idle(或收到 record 事件)再认为文件已经写完;此时立刻重启录音会得到 invalid_state。

示例

voip.recordStop()

4.15 voip.recordStatus()

功能

查询录音状态。

参数

无。

返回值

含义说明:录音状态表
数据类型:table
取值范围:字段见下
返回示例:{state = "recording", path = "/luadb/call.wav", bytes = 320000, duration_ms = 10000, queued_frames = 0, dropped_frames = 0, reason = nil}
字段 含义
state 录音状态字符串(idle/recording/stopping 等,由内部状态名给出)
path 当前录音路径
bytes 已写入字节数
duration_ms 已录时长(毫秒)
queued_frames 待写队列帧数,持续变大说明写卡跟不上
dropped_frames 因队列满丢弃的帧数,大于 0 表示录音有缺口
reason 停止/失败原因,正常为 nil

4.16 voip.setAudioMode(mode)

功能

切换音频工作模式:硬件直驱(I2S)或纯 PCM 桥接。

参数

mode

参数含义:音频模式
数据类型:number
取值范围:voip.AUDIO_MODE_I2S(0) / voip.AUDIO_MODE_BRIDGE(1)
是否必选:是
注意事项:桥接模式需要固件开 LUAT_USE_VOIP_BRIDGE,否则该常量与 pcmIn/pcmOut 都是 nil。

返回值

含义说明:是否切换成功
数据类型:boolean
取值范围:true/false
返回示例:true

注意事项

应在 voip.start 之前调用;通话中途切换会导致音频通路上出现杂音或静音。

示例

voip.setAudioMode(voip.AUDIO_MODE_BRIDGE)

4.17 voip.pcmIn(data)

功能

桥接模式下的上行注入:把外部采集的 PCM 送给 voip,由它编码后按 RTP 发出去。

参数

data

参数含义:PCM 数据
数据类型:string
取值范围:16bit 单声道小端;长度应为偶数
是否必选:是
注意事项:奇数长度时尾部半个样本被忽略(按 len/2 计算样本数)。

返回值

含义说明:实际被消耗的样本数
数据类型:number
取值范围:0 ~ len/2;0 表示本次没吃进去(缓冲满)
注意事项:返回值小于传入样本数时,说明尾部没被消费,要保留下来下次再送,不能直接丢掉。
返回示例:160

示例

local consumed = voip.pcmIn(pcm_chunk)
if consumed < #pcm_chunk / 2 then
    keep_tail(pcm_chunk, consumed)   -- 保留未消费的尾部
end

4.18 voip.pcmOut(max_samples)

功能

桥接模式下的下行取出:把对端发来的语音解码成 PCM 交给脚本播放。

参数

max_samples

参数含义:本次最多请求的样本数
数据类型:number
取值范围:大于 0
是否必选:是
注意事项:按 20ms@8kHz 算,一帧是 160 样本。

返回值

含义说明:解码后的 PCM 数据
数据类型:string 或 nil
取值范围:有数据返回 PCM16 小端数据;无数据、参数为 0 或内存不足返回 nil
注意事项:nil 是正常情况(还没收到包),不要当错误处理,下一轮继续取。
返回示例:"\x00\x01..."(320 字节 = 160 样本)

示例

local pcm = voip.pcmOut(160)
if pcm then
    audio_play(pcm)
end

4.19 voip.pcmResampler(input_rate, output_rate, gain)

功能

创建有状态的采样率转换器,用于把 16kHz 的麦克风数据降到 8kHz 送给 voip,或把 8kHz 的下行升到 16kHz 播放。

参数

input_rate / output_rate

参数含义:输入与输出采样率
数据类型:number
取值范围:仅支持 16000→8000 或 8000→16000
是否必选:是
注意事项:其它组合直接抛 Lua 错误(lua_error),不是返回 nil。

gain

参数含义:转换增益
数据类型:number
取值范围:16k→8k 时为 1~8,默认 1;8k→16k 时只能为 1
是否必选:否
注意事项:越界抛参数错误。

返回值

含义说明:转换器对象(userdata)
数据类型:userdata
取值范围:提供 :process(data) 与 :reset() 两个方法
返回示例:userdata: 0x...

对象方法:

方法 说明
rs:process(data) 返回转换后的 PCM 字符串与本次被限幅的样本数;输入为空返回空串与 0;块长度非法抛参数错误且不改变转换器状态
rs:reset() 清空滤波历史(通话开始/结束时调用)

注意事项

1、16k→8k 用 7 抽头二项式低通抽取,8k→16k 用相邻样本线性插值,两者都是有状态的,滤波历史跨 process 调用保留,所以不能中途新建实例,否则会有咔哒声;

2、每次 16k 输入最多 512 字节且长度为 4 的倍数,8k 输入最多 320 字节且长度为 2 的倍数;

3、每路音频用独立实例,不要共用;通话开始与结束时各 reset() 一次。

示例

local mic = voip.pcmResampler(16000, 8000, 1)
local spk = voip.pcmResampler(8000, 16000)
local pcm8k = mic:process(pcm16k_512)
voip.pcmIn(pcm8k)
local down = voip.pcmOut(160)
if down then
    local pcm16k = spk:process(down)
end

4.20 voip.bridgeTone(on)

功能

控制桥接模式内部产生的早期提示音(如拨号音/回铃音的本地生成)。

参数

on

参数含义:开关
数据类型:boolean
取值范围:true 启动,false 停止
是否必选:是

返回值

含义说明:是否成功
数据类型:boolean
取值范围:true/false
返回示例:true

示例

voip.bridgeTone(true)

4.21 voip.bridgeMute(on)

功能

静音运行中的纯 PCM 桥接双向通话语音,TTS/音频注入和媒体节拍不受影响——也就是“人声静音但播报照常”。

参数

on

参数含义:静音开关
数据类型:boolean
取值范围:true 静音原通话语音,false 恢复
是否必选:是
注意事项:必须传 boolean,传数字会抛类型错误。

返回值

含义说明:是否成功
数据类型:boolean
取值范围:true 成功;false 当前不在运行中的纯 PCM 桥接会话
返回示例:true

注意事项

只对 cc.AUDIO_MODE_BRIDGE_PCM 这个后端有效;会话启动和停止时静音状态自动恢复为未静音,不用手工复位。

示例

voip.bridgeMute(true)

4.22 voip.securityIdentity()

功能

创建运行期的 DTLS 证书身份,用于 DTLS-SRTP 安全协商。私钥不导出。

参数

无。

返回值

含义说明:身份句柄
数据类型:userdata
取值范围:成功返回句柄;失败返回 nil 与原因
返回示例:userdata: 0x...

注意事项

该函数需要 LUAT_USE_SRTP;句柄由 GC 自动释放,也可以显式 securityRelease。

4.23 voip.securityCreate(mode, identity)

功能

创建本地安全媒体协商材料。同一个 SDP 重传必须复用同一个句柄,不能每次重传都新建。

参数

mode

参数含义:安全模式
数据类型:string
取值范围:"sdes" 或 "dtls-srtp"
是否必选:是
注意事项:其它取值返回 nil 与 unsupported。

identity

参数含义:DTLS 运行期身份
数据类型:userdata,可省略
取值范围:voip.securityIdentity() 的返回值
是否必选:否(仅 dtls-srtp 需要)

返回值

含义说明:安全会话句柄
数据类型:userdata 或 nil + string
取值范围:成功返回句柄;失败返回 nil 与原因
返回示例:userdata: 0x...

4.24 voip.securityInfo(handle)

功能

取本地 SDP 协商信息,用于填到 SDP 的 a=crypto 或 a=fingerprint 行。

参数

handle

参数含义:安全会话句柄
数据类型:userdata
取值范围:voip.securityCreate 的返回值
是否必选:是

返回值

含义说明:协商信息表
数据类型:table 或 nil + string
取值范围:SDES 时为 {mode="sdes", suite=..., key=...};DTLS 时为 {mode="dtls-srtp", hash="sha-256", fingerprint=...}
返回示例:{mode = "sdes", suite = "AES_CM_128_HMAC_SHA1_80", key = "..."}

⚠️ SDES 的 key 是会话密钥,属敏感信息,只能通过经验证的 TLS 信令通道发送,明文信令里发出去等于把加密作废。

4.25 voip.securitySetRemote(handle, remote)

功能

用本地协商材料加上对端协商结果,生成一个已协商的独立会话句柄,交给 voip.start 的 security 字段使用。原句柄保持不变。

参数

handle

参数含义:本地安全会话句柄
数据类型:userdata
是否必选:是

remote

参数含义:对端协商结果
数据类型:table
取值范围:SDES 为 {mode="sdes", suite=..., key=...};DTLS 为 {mode="dtls-srtp", hash="sha-256", fingerprint=..., role="active"/"passive"}
是否必选:是
注意事项:mode 必须与本地一致,否则返回 invalid;SDES 的 key 文本长度、DTLS 的 fingerprint 文本长度都有固定校验,长度不对直接拒绝。

返回值

含义说明:新的已协商句柄
数据类型:userdata 或 nil + string
取值范围:成功返回新句柄;失败返回 nil 与原因
返回示例:userdata: 0x...

4.26 voip.securityRelease(handle)

功能

显式释放安全会话或身份句柄。

参数

handle

参数含义:安全会话或身份句柄
数据类型:userdata
是否必选:是

返回值

含义说明:是否释放成功
数据类型:boolean
取值范围:true 成功;false 句柄类型不对或已释放
返回示例:true

注意事项

句柄挂在 GC 上,正常流程可以不显式释放;重复调用是安全的(第二次返回 false)。

五、产品支持说明

1、voip 需要固件在编译时包含 VoIP 组件(LUAT_USE_VOIP)。目前合宙公开固件里带 SIP/voip 的主要是 EC7xx 的 13/113 号与 22/122 号固件:13/113 号用于纯 SIP 通话,22/122 号在此基础上带 airui 图形界面、SRTP 与 G.729/G.722 编解码;具体到某个型号是否支持,以该型号固件页的“固件支持核心库列表”为准;

2、voip.playTTS / playFile(V2056 起修复了“开了 TTS 却未开 ONCHIP 导致误走外置资源路径、TTS 播不出来”的问题)、record*、pcm*/bridge*、security* 分别由 LUAT_USE_VOIP_PLAY、LUAT_USE_VOIP_RECORD、LUAT_USE_VOIP_BRIDGE、LUAT_USE_SRTP 控制,脚本里对应函数为 nil 即表示该子功能未编入;

3、Air780EPM 这类不带 audio/codec 的型号不支持 voip(与 cc 的差异:cc 的能力表同样标不支持),需要 SIP 通话请选 Air780EHM/EHV/EGH/EGG 或 Air8784、Air8783H、Air8605、Air8701 这类做过音频处理的型号;

4、Air8101 系列不支持 voip。

以上判定最终以各型号固件页的核心库支持表与对接 FAE 的答复为准。

搜索