Files
BR_YKC/4G/源代码/lib/audio.lua
2026-05-21 13:24:05 +08:00

484 lines
18 KiB
Lua
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
--- 模块功能:音频播放.
-- 支持MP3、amr文件播放
-- 支持本地TTS播放、通话中TTS播放到对端需要使用支持TTS功能的core软件
-- @module audio
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2018.3.19
require "common"
require "misc"
require "utils"
module(..., package.seeall)
local req = ril.request
local stopCbFnc
--tts速度默认50
local ttsSpeed = 50
--喇叭音量和mic音量等级
local sVolume,sMicVolume = 4,1
local sCallVolume = 4
--音频播放的协程ID
local taskID
--播放和停止请求队列用于存储通过调用audio.play和audio.stop接口允许播放和停止播放的请求项
--每个播放请求项为table类型数据结构如下参考本文件中的play接口注释
--priority播放优先级
--type播放类型
--path播放音频内容
--vol播放音量
--cbFnc播放结束后的回调函数
--dup是否重复播放
--dupInterval重复播放的间隔单位毫秒
--每个停止请求项为table类型数据结构如下参考本文件中的stop接口注释
--type固定为"STOP"
--cbFnc停止播放后的回调函数
local audioQueue = {}
--sStrategy优先级相同时的播放策略0(表示继续播放正在播放的音频,忽略请求播放的新音频)1(表示停止正在播放的音频,播放请求播放的新音频)
local sStrategy
local function isTtsStopResultValid()
local version = string.match(rtos.get_version(),"(%d+)_RDA")
if version then
return tonumber(version)>=8
else
return false
end
end
local function handleCb(item,result)
log.info("audio.handleCb",item.cbFnc,result)
if item.cbFnc then item.cbFnc(result) end
table.remove(audioQueue,1)
end
local function handlePlayInd(item,key,value)
log.info("audio.handlePlayInd",key,value)
--播放结束
if key=="RESULT" then
--播放成功
if value then
if item.dup then
if item.dupInterval>0 then
log.info("audio.handlePlayInd",item.type,"dup wait LIB_AUDIO_PLAY_IND or timeout",item.dupInterval)
local result,reason = sys.waitUntil("LIB_AUDIO_PLAY_IND",item.dupInterval)
log.info("audio.handlePlayInd",item.type,"dup wait",reason or "timeout")
if result then
log.warn("audio.handlePlayInd",item.type,"dup wait error",reason)
handleCb(item,reason=="NEW" and 4 or 5)
end
end
else
handleCb(item,0)
end
--播放失败
else
log.warn("audio.handlePlayInd",item.type,"play cnf error")
handleCb(item,1)
end
--新的优先级更高的播放请求
elseif key=="NEW" then
log.warn("audio.handlePlayInd",item.type,"priority error")
handleCb(item,4)
--主动调用audio.stop
elseif key=="STOP" then
log.warn("audio.handlePlayInd",item.type,"stop error",result)
handleCb(item,5)
end
end
local ttsEngineInited
local audioTaskWaitPlayEntry
local function audioTask()
while true do
if #audioQueue==0 then
log.info("audioTask","wait LIB_AUDIO_PLAY_ENTRY")
audioTaskWaitPlayEntry = true
sys.waitUntil("LIB_AUDIO_PLAY_ENTRY")
audioTaskWaitPlayEntry = false
end
local item = audioQueue[1]
log.info("audioTask",item.type,"#audioQueue",#audioQueue)
if item.type=="FILE" then
--队列中有优先级高的请求等待处理
if #audioQueue>1 then
log.warn("audioTask",item.type,"priority low")
local behind = audioQueue[2]
handleCb(item,behind.type=="STOP" and 5 or 4)
else
setVolume(item.vol)
local result
if type(item.path)=="table" then
if (item.path[1]):match("%.amr$") or (item.path[1]):match("%.AMR$") then
local dataBuf = {}
for i=1,#item.path do
table.insert(dataBuf,(io.readFile(item.path[i])):sub(i==1 and 1 or 7,-1))
end
result = audiocore.playdata(table.concat(dataBuf),audiocore.AMR)
elseif (item.path[1]):match("%.pcm$") or (item.path[1]):match("%.PCM$") then
local dataBuf = {}
for i=1,#item.path do
table.insert(dataBuf,io.readFile(item.path[i]))
end
result = audiocore.playdata(table.concat(dataBuf),audiocore.PCM)
elseif (item.path[1]):match("%.mp3$") or (item.path[1]):match("%.MP3$") then
local dataBuf = {}
for i=1,#item.path do
table.insert(dataBuf,io.readFile(item.path[i]))
end
result = audiocore.playdata(table.concat(dataBuf),audiocore.MP3)
elseif (item.path[1]):match("%.wav$") or (item.path[1]):match("%.WAV$") then
local dataBuf = {}
for i=1,#item.path do
table.insert(dataBuf,io.readFile(item.path[i]))
end
result = audiocore.playdata(table.concat(dataBuf),audiocore.WAV)
else
result = false
end
--result = audiocore.play(unpack(item.path))
else
if item.path:match("%.wav$") or item.path:match("%.WAV$") then
result = audiocore.playdata(io.readFile(item.path),audiocore.WAV)
else
result =audiocore.play(item.path)
end
end
if result then
--等待三种消息播放结束、主动调用audio.stop、新的优先级更高的播放请求
log.info("audioTask",item.type,"wait LIB_AUDIO_PLAY_IND")
local _,key,value = sys.waitUntil("LIB_AUDIO_PLAY_IND")
log.info("audioTask",item.type,"recv LIB_AUDIO_PLAY_IND",key,value)
audiocore.stop()
handlePlayInd(item,key,value)
else
log.warn("audioTask",item.type,"audiocore.play error")
audiocore.stop()
handleCb(item,1)
end
end
elseif item.type=="TTS" or item.type=="TTSCC" then
--队列中有优先级高的请求等待处理
if #audioQueue>1 then
log.warn("audioTask",item.type,"priority low")
local behind = audioQueue[2]
handleCb(item,behind.type=="STOP" and 5 or 4)
else
setVolume(item.vol)
if item.type=="TTS" then
if not ttsEngineInited then
ttsply.initEngine()
ttsEngineInited = true
end
ttsply.setParm(0,ttsSpeed)
--队列中有优先级高的请求等待处理
if #audioQueue>1 then
log.warn("audioTask",item.type,"priority low1")
if isTtsStopResultValid() then
if ttsply.stop() then
sys.waitUntil("LIB_AUDIO_PLAY_IND",2000)
end
else
ttsply.stop()
sys.waitUntil("LIB_AUDIO_PLAY_IND",500)
end
local behind = audioQueue[2]
handleCb(item,behind.type=="STOP" and 5 or 4)
else
ttsply.play(common.utf8ToGb2312(item.path))
--等待三种消息播放结束、主动调用audio.stop、新的优先级更高的播放请求
log.info("audioTask",item.type,"wait LIB_AUDIO_PLAY_IND")
local _,key,value = sys.waitUntil("LIB_AUDIO_PLAY_IND")
log.info("audioTask",item.type,"recv LIB_AUDIO_PLAY_IND",key,value)
if item.type=="TTS" then
if isTtsStopResultValid() then
--log.info("tts 1")
if ttsply.stop() then
--log.info("tts 2")
sys.waitUntil("LIB_AUDIO_PLAY_IND",2000)
end
--log.info("tts 3")
else
ttsply.stop()
sys.waitUntil("LIB_AUDIO_PLAY_IND",500)
end
else
end
handlePlayInd(item,key,value)
end
else
end
end
elseif item.type=="RECORD" then
--队列中有优先级高的请求等待处理
if #audioQueue>1 then
log.warn("audioTask",item.type,"priority low")
local behind = audioQueue[2]
handleCb(item,behind.type=="STOP" and 5 or 4)
else
setVolume(item.vol)
f,d=record.getSize()
req("AT+AUDREC=1,0,2,"..item.path..","..d*1000)
--等待三种消息播放结束、主动调用audio.stop、新的优先级更高的播放请求
log.info("audioTask",item.type,"wait LIB_AUDIO_PLAY_IND")
local _,key,value = sys.waitUntil("LIB_AUDIO_PLAY_IND")
log.info("audioTask",item.type,"recv LIB_AUDIO_PLAY_IND",key,value)
req("AT+AUDREC=1,0,3,"..item.path..","..d*1000)
sys.waitUntil("LIB_AUDIO_RECORD_STOP_RESULT")
handlePlayInd(item,key,value)
end
elseif item.type=="STOP" then
if item.cbFnc then item.cbFnc(0) end
table.remove(audioQueue,1)
end
end
end
--- 播放音频
-- @number priority 音频优先级,数值越大,优先级越高
-- 优先级高的播放请求会终止优先级低的播放
-- 相同优先级的播放请求播放策略参考audio.setStrategy接口
-- @string type 音频类型,目前仅支持"FILE"、"TTS"
-- @string path 音频文件路径跟typ有关
-- typ为"FILE"时:表示音频文件路径
-- typ为"TTS"时表示要播放的UTF8编码格式的数据
-- @number[opt=4] vol 播放音量取值范围0到70为静音
-- @function[opt=nil] cbFnc 音频播放结束时的回调函数,回调函数的调用形式如下:
-- cbFnc(result)
-- result表示播放结果
-- 0-播放成功结束;
-- 1-播放出错
-- 2-播放优先级不够,没有播放
-- 3-传入的参数出错,没有播放
-- 4-被新的播放请求中止
-- 5-调用audio.stop接口主动停止
-- @bool[opt=nil] dup 是否循环播放true循环false或者nil不循环
-- @number[opt=0] dupInterval 循环播放间隔(单位毫秒)dup为true时此值才有意义
-- @return resultbool或者nil类型同步调用成功返回true否则返回false
-- @usage audio.play(0,"FILE","/lua/call.mp3")
-- @usage audio.play(0,"FILE","/lua/call.mp3",7)
-- @usage audio.play(0,"FILE","/lua/call.mp3",7,cbFnc)
-- @usage 更多用法参考demo/audio/testAudio.lua
function play(priority,type,path,vol,cbFnc,dup,dupInterval)
log.info("audio.play",priority,type,path,vol,cbFnc,dup,dupInterval)
if not taskID then
taskID = sys.taskInit(audioTask)
end
local item = {priority=priority,type=type,path=path,vol=vol or 4,cbFnc=cbFnc,dup=dup,dupInterval=dupInterval or 0}
if #audioQueue==0 then
table.insert(audioQueue,item)
sys.publish("LIB_AUDIO_PLAY_ENTRY")
else
local front = audioQueue[#audioQueue]
if front.type=="STOP" then
table.insert(audioQueue,item)
else
if priority>front.priority or (priority==front.priority and sStrategy==1) then
table.insert(audioQueue,item)
if not audioTaskWaitPlayEntry then
sys.publish("LIB_AUDIO_PLAY_IND","NEW")
end
else
log.warn("audio.play","priority error")
if cbFnc then cbFnc(2) end
end
end
end
return true
end
--- 停止音频播放
-- @function[opt=nil] cbFnc 停止音频播放的回调函数(停止结果通过此函数通知用户),回调函数的调用形式为:
-- cbFnc(result)
-- resultnumber类型
-- 0表示停止成功
-- @return nil
-- @usage audio.stop()
function stop(cbFnc)
log.info("audio.stop",cbFnc)
if #audioQueue==0 then
if cbFnc then cbFnc(0) end
else
table.insert(audioQueue,{type="STOP",cbFnc=cbFnc})
sys.publish("LIB_AUDIO_PLAY_IND","STOP")
end
end
local function audioMsg(msg)
--log.info("audio.MSG_AUDIO",msg.play_end_ind,msg.play_error_ind)
sys.publish("LIB_AUDIO_PLAY_IND","RESULT",msg.play_end_ind)
end
--注册core上报的rtos.MSG_AUDIO消息的处理函数
rtos.on(rtos.MSG_AUDIO,audioMsg)
rtos.on(rtos.MSG_TTSPLY_STATUS, function() log.info("rtos.MSG_TTSPLY_STATUS") sys.publish("LIB_AUDIO_PLAY_IND","RESULT",true) end)
rtos.on(rtos.MSG_TTSPLY_ERROR, function() log.info("rtos.MSG_TTSPLY_ERROR") sys.publish("LIB_AUDIO_PLAY_IND","RESULT",false) end)
--- 设置喇叭音量等级
-- @number vol 音量值为0-70为静音
-- @return bool result设置成功返回true失败返回false
-- @usage audio.setVolume(7)
function setVolume(vol)
local result = audiocore.setvol(vol)
if result == 1 then
result = true
elseif result == 0 then
result = false
end
if result then sVolume = vol end
return result
end
--- 设置通话音量等级
-- @number vol 音量值为0-70为静音
-- @return bool result设置成功返回true失败返回false
-- @usage audio.setCallVolume(7)
function setCallVolume(vol)
--local result = audiocore.setsphvol(vol)
--if result then sCallVolume = vol end
--return result
audiocore.setsphvol(vol)
sCallVolume = vol
return true
end
-- 设置麦克音量等级
-- @number vol音量值为0-150为静音
-- @return bool result设置成功返回true,失败返回false
-- @usage audio.setMicVolume(14)
function setMicVolume(vol)
ril.request("AT+CMIC="..audiocore.LOUDSPEAKER..","..vol)
return true
end
ril.regRsp("+CMIC",function(cmd,success)
if success then
sMicVolume = tonumber(cmd:match("CMIC=%d+,(%d+)"))
end
end)
--- 设置mic增益等级
-- 通话时mic增益在通话建立成功之后设置才有效
-- 录音mic增益设置后实时生效
-- @string mode 增益类型
-- "call"表示通话中mic增益
-- "record"表示录音mic增益
-- @number level 增益等级取值为0-7
-- @return bool result设置成功返回true失败返回false
-- @usage audio.setMicGain("record",7)设置录音时mic增益为7级
function setMicGain(mode, level)
if (mode ~= "call" and mode ~= "record") or (level > 7 and level < 0) then
return false
else
local gainHex
if level == 7 then
gainHex = string.format("%02X%02X%02X%02X", 7, 0, 15, 0)
else
gainHex = string.format("%02X%02X%02X%02X", level, 0, level * 2, 0)
end
if mode == "call" then
ril.request("AT+CACCP=5,1,0," .. gainHex)
ril.request("AT+CACCP=0,1,0," .. gainHex)
elseif mode == "record" then
ril.request("AT+CACCP=2,1,6," .. gainHex)
end
return true
end
end
--- 获取喇叭音量等级
-- @return number vol喇叭音量等级
-- @usage audio.getVolume()
function getVolume()
return sVolume
end
--- 获取通话音量等级
-- @return number vol通话音量等级
-- @usage audio.getCallVolume()
function getCallVolume()
return sCallVolume
end
-- 获取麦克音量等级
-- @return number vol麦克音量等级
-- @usage audio.getMicVolume()
function getMicVolume()
return sMicVolume
end
--- 设置优先级相同时的播放策略
-- @number strategy 优先级相同时的播放策略;
-- 0表示继续播放正在播放的音频忽略请求播放的新音频
-- 1表示停止正在播放的音频播放请求播放的新音频
-- @return nil
-- @usage audio.setStrategy(0)
-- @usage audio.setStrategy(1)
function setStrategy(strategy)
sStrategy=strategy
end
--- 设置TTS朗读速度
-- @number speed 速度范围为0-100默认50
-- @return bool result设置成功返回true失败返回false
-- @usage audio.setTTSSpeed(70)
function setTTSSpeed(speed)
if type(speed) == "number" and speed >= 0 and speed <= 100 then
ttsSpeed = speed
return true
end
end
--- 设置音频输入、输出通道
-- 设置后实时生效
-- @number[opt=2] output 0earphone听筒1headphone耳机2speaker喇叭
-- @number[opt=0] input 0主mic3耳机mic
-- @return nil
-- @usage
-- 设置为听筒输出audio.setChannel(0)
-- 设置为耳机输出audio.setChannel(1)
-- 设置为喇叭输出audio.setChannel(2)
-- 设置为喇叭输出、耳机mic输入audio.setChannel(2,3)
function setChannel(output, input)
local version = string.match(rtos.get_version(), "(%d+)_RDA")
if not version or tonumber(version) >= 9 then --匹配不到,兼容其它版本 或者大于版本9
audiocore.setchannel(output or 2, input or 0)
else
ril.request("AT+AUDCH="..(output==1 and 1 or 2))
end
end
--默认音频通道设置为LOUDSPEAKER因为目前的模块只支持LOUDSPEAKER通道
audiocore.setchannel(audiocore.LOUDSPEAKER)
--默认音量等级设置为4级4级是中间等级最低为0级最高为7级
setVolume(sVolume)
setCallVolume(sCallVolume)
--默认MIC音量等级设置为1级最低为0级最高为15级
setMicVolume(sMicVolume)