跳转至

录音

一、录音概述

录音技术是通过麦克风将声波振动转换为模拟电信号,经模数转换(ADC)采样、量化后编码为数字音频(如 WAV、MP3 格式),并存储于磁带、光盘或闪存介质中的过程,核心环节包括声学采集(指向性麦克风)、信号放大、降噪处理(如 DSP 算法)、动态范围压缩及编解码优化,现代技术进一步融合高保真(Hi-Res)、空间音频(3D 录音)与云端同步能力,广泛应用于音乐制作、通信、安防及多媒体领域。

二、演示功能概述

本篇文章演示的内容为:Air8000 整机开发板,接上喇叭,录制音频,然后播放,播放完之后把录音文件通过串口 1 发送出去

三、准备硬件环境

参考:Air8000 硬件环境清单,准备好硬件环境,本篇文档使用的是 Air8000 整机开发板,如下图所示:

四、准备软件环境

烧录工具:Luatools 工具

Air8000 烧录需要的固件和脚本文件:

LuatOS 运行所需要的 lib 文件:

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

五、代码 API 和代码解析

5.1 代码 API

mcu.altfun(type, sn, pad_index, alt_fun, is_input)

IO 外设功能复用选择,注意普通 MCU 通常是以 GPIO 号为唯一 ID 号,但是专用 SOC,比如 CAT1 的,可能以 PAD 号或者模块 pin 脚号(pin.xxx 后续支持)为唯一 ID 号。本函数不是所有平台适用

参数

传入值类型
解释
int
外设类型,目前有mcu.UART,mcu.I2C,mcu.SPI,mcu.PWM,mcu.GPIO,mcu.I2S,mcu.LCD,mcu.CAM,具体需要看平台
int
总线序号,0~N,如果是mcu.GPIO,则是GPIO号。具体看平台的IOMUX复用表
int
唯一ID号,如果留空不写,则表示清除配置,使用平台的默认配置。具体看平台的IOMUX复用表
int
复用功能序号,0~N。具体看平台的IOMUX复用表
boolean
是否是输入功能,true是,留空是false

返回值

例子

mcu.altfun(mcu.I2C, 0, 13, 2, 0)    --复用pin80为i2c0_scl
mcu.altfun(mcu.I2C, 0, 14, 2, 0)    --复用pin81为i2c0_sda

audio.config(id, paPin, onLevel, dacDelay, paDelay, dacPin, dacLevel, dacTimeDelay)

配置一个音频通道的特性,比如实现自动控制 PA 开关。注意这个不是必须的,一般在调用 play 的时候才需要自动控制,其他情况比如你手动控制播放时,就可以自己控制 PA 开关

参数

传入值类型
解释
int
音频通道
int
PA控制IO
int
PA打开时的电平
int
在DAC启动前插入的冗余时间,单位100ms,一般用于外部DAC
int
在DAC启动后,延迟多长时间打开PA,单位1ms
int
外部dac电源控制IO
int
外部dac打开时,电源控制IO的电平,默认拉高
int
音频播放完毕时,PA与DAC关闭的时间间隔,单位1ms,默认0ms

返回值

例子

audio.config(0, pin.PC0, 1)    --PA控制脚是PC0,高电平打开   
audio.config(0, 25, 1, 6, 200)    --PA控制脚是GPIO25,高电平打开

codec.create(type, isDecoder, quality)

创建编解码用的 codec

参数

传入值类型
解释
int
多媒体类型,目前支持codec.MP3 codec.AMR
boolean
是否是解码器,true解码器,false编码器,默认true,是解码器
int
编码等级,部分bsp有内部编解码器,可能需要提前输入编解码等级,不知道的就填7

返回值

返回值类型
解释
userdata
成功返回一个数据结构,否则返回nil

例子

-- 创建解码器
local decoder = codec.create(codec.MP3)--创建一个mp3的decoder
-- 创建编码器
local encoder = codec.create(codec.AMR, false)--创建一个amr的encoder
-- 创建编码器
local encoder = codec.create(codec.AMR_WB, false, 8)--创建一个amr-wb的encoder,编码等级默认8

codec.encode(coder, in_buffer, out_buffer, mode)

编码音频数据,由于 flash 和 ram 空间一般比较有限,除了部分 bsp 有内部 amr 编码功能,目前只支持 amr-nb 编码

参数

传入值类型
解释
userdata
codec.create创建的编解码用的coder
zbuff
输入的数据,zbuff形式,从0到used
zbuff
输出的数据,zbuff形式,自动添加到buff的尾部,如果空间大小不足,会自动扩展,但是会额外消耗时间,甚至会失败,所以尽量一开始就给足空间
int
amr_nb的编码等级 0~7(即 MR475~MR122)值越大消耗的空间越多,音质越高,默认0 amr_wb的编码等级 0~8,值越大消耗的空间越多,音质越高,默认0

返回值

返回值类型
解释
boolean
成功返回true,失败返回false

例子

codec.encode(amr_coder, inbuf, outbuf)

i2s.setup(id, mode, sample, bitw, channel, format, framebit)

初始化 i2s

参数

传入值类型
解释
int
i2s通道号,与具体设备有关
int
模式, 0 主机 1 从机
int
采样率,默认44100. 可选
int
数据位数,默认16, 可以是8的倍数
int
声道, 0 左声道, 1 右声道, 2 立体声. 可选
int
格式, 可选MODE_I2S, MODE_LSB, MODE_MSB
int
1个声道的BCLK数量, 可选16和32

返回值

返回值类型
解释
boolean
成功与否
int
底层返回值

例子

-- 以默认参数初始化i2s
i2s.setup(0)
-- 以详细参数初始化i2s, 示例为默认值
i2s.setup(0, 0, 44100, 16, 0, 0, 16)

codec.release(coder)

释放编解码用的 coder

参数

传入值类型
解释
coder
codec.create创建的编解码用的coder

返回值

例子

codec.release(coder)

audio.vol(id, value)

配置一个音频通道的音量调节,直接将原始数据放大或者缩小,不是所有平台都支持,建议尽量用硬件方法去缩放

参数

传入值类型
解释
int
音频通道
int
音量,百分比,1%~1000%,默认100%,就是不调节

返回值

返回值类型
解释
int
当前音量

例子

local result = audio.vol(0, 90)    --通道0的音量调节到90%,result存放了调节后的音量水平,有可能仍然是100

audio.micVol(id, value)

配置一个音频通道的 mic 音量调节

参数

传入值类型
解释
int
音频通道
int
mic音量,百分比,1%~100%,默认100%,就是不调节

返回值

返回值类型
解释
int
当前mic音量

例子

local result = audio.vol(0, 90)    --通道0的音量调节到90%,result存放了调节后的音量水平,有可能仍然是100

audio.getError(id)

获取最近一次播放结果

参数

传入值类型
解释
int
音频通道

返回值

返回值类型
解释
boolean
是否全部播放成功,true成功,false有文件播放失败
boolean
如果播放失败,是否是用户停止,true是,false不是
int
第几个文件失败了,从1开始

例子

local result, user_stop, file_no = audio.getError(0)

audio.play(id, path, errStop)

播放或者停止播放一个文件,播放完成后,会回调一个 audio.DONE 消息,可以用 pause 来暂停或者恢复,其他 API 不可用。考虑到读 SD 卡速度比较慢而拖累 luavm 进程的速度,所以尽量使用本 API

参数

传入值类型
解释
int
音频通道
string/table
文件名,如果为空,则表示停止播放,如果是table,则表示连续播放多个文件,主要应用于云喇叭
boolean
是否在文件解码失败后停止解码,只有在连续播放多个文件时才有用,默认true,遇到解码错误自动停止

返回值

返回值类型
解释
boolean
成功返回true,否则返回false

例子

audio.play(0, "xxxxxx")        --开始播放某个文件

audio.setBus(id, bus_type)

配置一个音频通道的硬件输出总线,只有对应 soc 软硬件平台支持才设置对应类型

参数

传入值类型
解释
int
音频通道,例如0
int
总线类型, 例如 audio.BUS_SOFT_DAC
int
硬件id, 例如 总线类型为audio.BUS_I2S时,硬件id即为i2s codec的i2c id

返回值

返回值类型
解释
nil
无返回值

例子

audio.setBus(0, audio.BUS_SOFT_DAC)    --通道0的硬件输出通道设置为软件DAC
audio.setBus(0, audio.BUS_I2S)    --通道0的硬件输出通道设置为I2S

audio.on(audio_id, func)

注册 audio 播放事件回调

参数

传入值类型
解释
int
audio id, audio 0写0, audio 1写1
function
回调方法,回调时传入参数为1、int 通道ID 2、int 消息值,有audio.MORE_DATA,audio.DONE,audio.RECORD_DATA,audio.RECORD_DONE,3、RECORD_DATA后面跟数据存在哪个zbuff内,0或者1
常量
类型
解释
audio.MORE_DATA
number
audio.on回调函数传入参数的值,表示底层播放完一段数据,可以传入更多数据
audio.DONE
number
audio.on回调函数传入参数的值,表示底层播放完全部数据了
audio.RECORD_DATA
number
audio.on回调函数传入参数的值,表示录音数据
audio.RECORD_DONE
number
audio.on回调函数传入参数的值,表示录音完成

返回值

返回值类型
解释
nil
无返回值

例子

audio.on(0, function(audio_id, msg)
    log.info("msg", audio_id, msg)
end)

audio.record(id, record_type, record_time, amr_quailty, path, record_callback_time, buff0, buff1,channelCount)

录音

参数

传入值类型
解释
int
id 多媒体播放通道号
int
record_type 录音音频格式,支持 audio.AMR audio.PCM (部分平台支持audio.AMR_WB),或者直接输入采样率
int
record_time 录制时长 单位秒,可选,默认0即表示一直录制
int
amr_quailty 质量,audio.AMR下有效
string
path 录音文件路径,可选,不指定则不保存,可在audio.on回调函数中处理原始PCM数据
int
record_callback_time 不指定录音文件路径时,单次录音回调时长,单位是100ms。默认1,既100ms
zbuff
录音原始PCM数据缓存0,不填写录音文件路径才会用到
zbuff
录音原始PCM数据缓存1,不填写录音文件路径才会用到
channelCount
声道数量,只针对非I2S设备有效,1单声道录音 2立体声录音 默认单声道.I2S设备在I2S相关API里配置

返回值

返回值类型
解释
boolean
成功返回true,否则返回false

例子

err,info = audio.record(id, type, record_time, quailty, path)

5.2 代码解析

1.复用 pin80 和 pin81 为 i2c0

mcu.altfun(mcu.I2C, 0, 13, 2, 0)
mcu.altfun(mcu.I2C, 0, 14, 2, 0)

2.设置 audio.on 的回调函数,根据 event 对录音数据的 point 存在对应的 buff 里面,再根据 event 判断是否为录音完成,录音完成通过 sys.publish 发送内部消息,激活录音完成之后的等待,然后根据播放结果,返回对应内容

local taskName = "task_audio"

local MSG_MD = "moreData"   -- 播放缓存有空余
local MSG_PD = "playDone"   -- 播放完成所有数据

-- amr数据存放buffer,尽可能地给大一些
amr_buff = zbuff.create(20 * 1024)
--创建一个amr的encoder
encoder = nil
pcm_buff0 = zbuff.create(16000)
pcm_buff1 = zbuff.create(16000)
audio.on(0, function(id, event, point)
    --使用play来播放文件时只有播放完成回调
    if event == audio.RECORD_DATA then -- 录音数据
        if point == 0 then
            log.info("buff", point, pcm_buff0:used())
            codec.encode(encoder, pcm_buff0, amr_buff)
        else
            log.info("buff", point, pcm_buff1:used())
            codec.encode(encoder, pcm_buff1, amr_buff)
        end

    elseif event == audio.RECORD_DONE then -- 录音完成
        sys.publish("AUDIO_RECORD_DONE")
    else
        local succ,stop,file_cnt = audio.getError(0)
        if not succ then
            if stop then
                log.info("用户停止播放")
            else
                log.info("第", file_cnt, "个文件解码失败")
            end
        end
        -- log.info("播放完成一个音频")
        sysplus.sendMsg(taskName, MSG_PD)
    end
end)

3.通过 HTTP 的 post 方式,利用 multipart/form-data 的方式,把录音文件传到服务器上。

---- MultipartForm上传文件
-- url string 请求URL地址
-- filename string 上传服务器的文件名
-- filePath string 待上传文件的路径
local function postMultipartFormData(url, filename, filePath)
    local boundary = "----WebKitFormBoundary"..os.time()
    local req_headers = {
        ["Content-Type"] = "multipart/form-data; boundary=" .. boundary,
    }
    local body = {}
    table.insert(body, "--"..boundary.."\r\nContent-Disposition: form-data; name=\"file\"; filename=\"".. filename .."\"\r\n\r\n")
    table.insert(body, io.readFile(filePath))
    table.insert(body, "\r\n")
    table.insert(body, "--"..boundary.."--\r\n")
    body = table.concat(body)
    log.info("headers: ", "\r\n" .. json.encode(req_headers), type(body))
    log.info("body: " .. body:len() .. "\r\n" .. body)
    local code, headers, body = http.request("POST",url,
            req_headers,
            body
    ).wait()   
    log.info("http.post", code, headers, body)
end

-- 下面的演示是将音频文件发送到服务器上,如有需要,可以将下面代码注释打开,这里的url是合宙的文件上传测试服务器,上传的文件到http://tools.openluat.com/tools/device-upload-test查看
    --[[ 
        local timeTable = os.date("*t", os.time())
        local nowTime = string.format("%4d%02d%02d_%02d%02d%02d", timeTable.year, timeTable.month, timeTable.day, timeTable.hour, timeTable.min, timeTable.sec)
        local filename = mobile.imei() .. "_" .. nowTime .. ".amr"
        postMultipartFormData("http://tools.openluat.com/api/site/device_upload_file", filename, recordPath)
    ]]

4.设置 i2s 和 audio 的一些参数,控制 8311 上电,然后设置 i2c,i2s,audio 的一些基础配置。

function audio_setup()
        local i2c_id = 0            -- i2c_id 0

        local pa_pin = 16           -- 喇叭pa功放脚
        local power_pin = 8         -- es8311电源脚

        local i2s_id = 0            -- i2s_id 0
        local i2s_mode = 0          -- i2s模式 0 主机 1 从机
        local i2s_sample_rate = 16000   -- 采样率
        local i2s_bits_per_sample = 16  -- 数据位数
        local i2s_channel_format = i2s.MONO_R   -- 声道, 0 左声道, 1 右声道, 2 立体声
        local i2s_communication_format = i2s.MODE_LSB   -- 格式, 可选MODE_I2S, MODE_LSB, MODE_MSB
        local i2s_channel_bits = 16     -- 声道的BCLK数量

        local multimedia_id = 0         -- 音频通道 0
        local pa_on_level = 1           -- PA打开电平 1 高电平 0 低电平
        local power_delay = 3           -- 在DAC启动前插入的冗余时间,单位100ms
        local pa_delay = 100            -- 在DAC启动后,延迟多长时间打开PA,单位1ms
        local power_on_level = 1        -- 电源控制IO的电平,默认拉高
        local power_time_delay = 100    -- 音频播放完毕时,PA与DAC关闭的时间间隔,单位1ms

        local voice_vol = 70        -- 喇叭音量
        local mic_vol = 80          -- 麦克风音量
        pm.power(pm.LDO_CTL, false)  --开发板上ES8311由LDO_CTL控制上下电
        sys.wait(100)
        pm.power(pm.LDO_CTL, true)  --开发板上ES8311由LDO_CTL控制上下电
        gpio.setup(8, 1)

        i2c.setup(i2c_id,i2c.FAST)      --设置i2c
        i2s.setup(i2s_id, i2s_mode, i2s_sample_rate, i2s_bits_per_sample, i2s_channel_format, i2s_communication_format,i2s_channel_bits)    --设置i2s

        audio.config(multimedia_id, pa_pin, pa_on_level, power_delay, pa_delay, power_pin, power_on_level, power_time_delay)
        audio.setBus(multimedia_id, audio.BUS_I2S,{chip = "es8311",i2cid = i2c_id , i2sid = i2s_id, voltage = audio.VOLTAGE_1800})  --通道0的硬件输出通道设置为I2S

        audio.vol(multimedia_id, voice_vol)
        audio.micVol(multimedia_id, mic_vol)
        sys.publish("AUDIO_READY")
end

-- 配置好audio外设
sys.taskInit(audio_setup)

5.本函数主要有两种录音的方式,第一种是直接录音到文件里面,然后等待录音完成的激活,然后播放音频,等待播放完成的激活,第二种方式是录音到内存,通过数据处理的方式保存成文件,等待录音完成的激活,然后播放音频,等待播放完成的激活,最后有两个对录音文件的扩展,第一个是上面已经讲解过的:利用 http 的方式把文件传到服务器上。第二个是:把录音文件通过串口 1,传到串口上。

local function audio_task()
    sys.waitUntil("AUDIO_READY")
    sys.wait(5000)
    local result

    --下面为录音demo,根据适配情况选择性开启
    local recordPath = "/record.amr"

    -- -- 直接录音到文件
    -- err = audio.record(0, audio.AMR, 5, 7, recordPath)
    -- sys.waitUntil("AUDIO_RECORD_DONE")
    -- log.info("record","录音结束")
    -- result = audio.play(0, {recordPath})
    -- if result then
    --     --等待音频通道的回调消息,或者切换歌曲的消息
    --     while true do
    --         msg = sysplus.waitMsg(taskName, nil)
    --         if type(msg) == 'table' then
    --             if msg[1] == MSG_PD then
    --                 log.info("播放结束")
    --                 break
    --             end
    --         else
    --             log.error(type(msg), msg)
    --         end
    --     end
    -- else
    --     log.debug("解码失败!")
    --     sys.wait(1000)
    -- end

    -- -- 录音到内存自行编码

    encoder = codec.create(codec.AMR, false, 7)
    log.info("encoder",encoder)
    log.info("开始录音")
    err = audio.record(0, audio.AMR, 5, 7, nil,nil, pcm_buff0, pcm_buff1)
    sys.waitUntil("AUDIO_RECORD_DONE")
    log.info("record","录音结束")
    os.remove(recordPath)
    io.writeFile(recordPath, "#!AMR\n")
    io.writeFile(recordPath, amr_buff:query(), "a+b")

    result = audio.play(0, {recordPath})
    if result then
        --等待音频通道的回调消息,或者切换歌曲的消息
        while true do
            msg = sysplus.waitMsg(taskName, nil)
            if type(msg) == 'table' then
                if msg[1] == MSG_PD then
                    log.info("播放结束")
                    break
                end
            else
                log.error(type(msg), msg)
            end
        end
    else
        log.debug("解码失败!")
        sys.wait(1000)
    end

    -- 下面的演示是将音频文件发送到服务器上,如有需要,可以将下面代码注释打开,这里的url是合宙的文件上传测试服务器,上传的文件到http://tools.openluat.com/tools/device-upload-test查看
    --[[ 
        local timeTable = os.date("*t", os.time())
        local nowTime = string.format("%4d%02d%02d_%02d%02d%02d", timeTable.year, timeTable.month, timeTable.day, timeTable.hour, timeTable.min, timeTable.sec)
        local filename = mobile.imei() .. "_" .. nowTime .. ".amr"
        postMultipartFormData("http://tools.openluat.com/api/site/device_upload_file", filename, recordPath)
    ]]
    --该方法为从串口1,把录音数据传给串口1
    --  uart.setup(1, 115200)                               -- 开启串口1
    --  uart.write(1, io.readFile(recordPath))              -- 向串口发送录音文件

end

六、运行结果展示

6.1 完整代码

-- LuaTools需要PROJECT和VERSION这两个信息
PROJECT = "record"
VERSION = "1.0.0"

-- sys库是标配
_G.sys = require("sys")
_G.sysplus = require("sysplus")

gpio.setup(24, 1, gpio.PULLUP)          -- i2c工作的电压域
mcu.altfun(mcu.I2C, 0, 13, 2, 0)
mcu.altfun(mcu.I2C, 0, 14, 2, 0)
local taskName = "task_audio"

local MSG_MD = "moreData"   -- 播放缓存有空余
local MSG_PD = "playDone"   -- 播放完成所有数据

-- amr数据存放buffer,尽可能地给大一些
amr_buff = zbuff.create(20 * 1024)
--创建一个amr的encoder
encoder = nil
pcm_buff0 = zbuff.create(16000)
pcm_buff1 = zbuff.create(16000)
audio.on(0, function(id, event, point)
    --使用play来播放文件时只有播放完成回调
    if event == audio.RECORD_DATA then -- 录音数据
        if point == 0 then
            log.info("buff", point, pcm_buff0:used())
            codec.encode(encoder, pcm_buff0, amr_buff)
        else
            log.info("buff", point, pcm_buff1:used())
            codec.encode(encoder, pcm_buff1, amr_buff)
        end

    elseif event == audio.RECORD_DONE then -- 录音完成
        sys.publish("AUDIO_RECORD_DONE")
    else
        local succ,stop,file_cnt = audio.getError(0)
        if not succ then
            if stop then
                log.info("用户停止播放")
            else
                log.info("第", file_cnt, "个文件解码失败")
            end
        end
        -- log.info("播放完成一个音频")
        sysplus.sendMsg(taskName, MSG_PD)
    end
end)

---- MultipartForm上传文件
-- url string 请求URL地址
-- filename string 上传服务器的文件名
-- filePath string 待上传文件的路径
local function postMultipartFormData(url, filename, filePath)
    local boundary = "----WebKitFormBoundary"..os.time()
    local req_headers = {
        ["Content-Type"] = "multipart/form-data; boundary=" .. boundary,
    }
    local body = {}
    table.insert(body, "--"..boundary.."\r\nContent-Disposition: form-data; name=\"file\"; filename=\"".. filename .."\"\r\n\r\n")
    table.insert(body, io.readFile(filePath))
    table.insert(body, "\r\n")
    table.insert(body, "--"..boundary.."--\r\n")
    body = table.concat(body)
    log.info("headers: ", "\r\n" .. json.encode(req_headers), type(body))
    log.info("body: " .. body:len() .. "\r\n" .. body)
    local code, headers, body = http.request("POST",url,
            req_headers,
            body
    ).wait()   
    log.info("http.post", code, headers, body)
end

function audio_setup()
        local i2c_id = 0            -- i2c_id 0

        local pa_pin = 16           -- 喇叭pa功放脚
        local power_pin = 8         -- es8311电源脚

        local i2s_id = 0            -- i2s_id 0
        local i2s_mode = 0          -- i2s模式 0 主机 1 从机
        local i2s_sample_rate = 16000   -- 采样率
        local i2s_bits_per_sample = 16  -- 数据位数
        local i2s_channel_format = i2s.MONO_R   -- 声道, 0 左声道, 1 右声道, 2 立体声
        local i2s_communication_format = i2s.MODE_LSB   -- 格式, 可选MODE_I2S, MODE_LSB, MODE_MSB
        local i2s_channel_bits = 16     -- 声道的BCLK数量

        local multimedia_id = 0         -- 音频通道 0
        local pa_on_level = 1           -- PA打开电平 1 高电平 0 低电平
        local power_delay = 3           -- 在DAC启动前插入的冗余时间,单位100ms
        local pa_delay = 100            -- 在DAC启动后,延迟多长时间打开PA,单位1ms
        local power_on_level = 1        -- 电源控制IO的电平,默认拉高
        local power_time_delay = 100    -- 音频播放完毕时,PA与DAC关闭的时间间隔,单位1ms

        local voice_vol = 70        -- 喇叭音量
        local mic_vol = 80          -- 麦克风音量
        pm.power(pm.LDO_CTL, false)  --开发板上ES8311由LDO_CTL控制上下电
        sys.wait(100)
        pm.power(pm.LDO_CTL, true)  --开发板上ES8311由LDO_CTL控制上下电
        gpio.setup(8, 1)

        i2c.setup(i2c_id,i2c.FAST)      --设置i2c
        i2s.setup(i2s_id, i2s_mode, i2s_sample_rate, i2s_bits_per_sample, i2s_channel_format, i2s_communication_format,i2s_channel_bits)    --设置i2s

        audio.config(multimedia_id, pa_pin, pa_on_level, power_delay, pa_delay, power_pin, power_on_level, power_time_delay)
        audio.setBus(multimedia_id, audio.BUS_I2S,{chip = "es8311",i2cid = i2c_id , i2sid = i2s_id, voltage = audio.VOLTAGE_1800})  --通道0的硬件输出通道设置为I2S

        audio.vol(multimedia_id, voice_vol)
        audio.micVol(multimedia_id, mic_vol)
        sys.publish("AUDIO_READY")
end

-- 配置好audio外设
sys.taskInit(audio_setup)

local function audio_task()
    sys.waitUntil("AUDIO_READY")
    sys.wait(5000)
    local result

    --下面为录音demo,根据适配情况选择性开启
    local recordPath = "/record.amr"

    -- -- 直接录音到文件
    -- err = audio.record(0, audio.AMR, 5, 7, recordPath)
    -- sys.waitUntil("AUDIO_RECORD_DONE")
    -- log.info("record","录音结束")
    -- result = audio.play(0, {recordPath})
    -- if result then
    --     --等待音频通道的回调消息,或者切换歌曲的消息
    --     while true do
    --         msg = sysplus.waitMsg(taskName, nil)
    --         if type(msg) == 'table' then
    --             if msg[1] == MSG_PD then
    --                 log.info("播放结束")
    --                 break
    --             end
    --         else
    --             log.error(type(msg), msg)
    --         end
    --     end
    -- else
    --     log.debug("解码失败!")
    --     sys.wait(1000)
    -- end

    -- -- 录音到内存自行编码

    encoder = codec.create(codec.AMR, false, 7)
    log.info("encoder",encoder)
    log.info("开始录音")
    err = audio.record(0, audio.AMR, 5, 7, nil,nil, pcm_buff0, pcm_buff1)
    sys.waitUntil("AUDIO_RECORD_DONE")
    log.info("record","录音结束")
    os.remove(recordPath)
    io.writeFile(recordPath, "#!AMR\n")
    io.writeFile(recordPath, amr_buff:query(), "a+b")

    result = audio.play(0, {recordPath})
    if result then
        --等待音频通道的回调消息,或者切换歌曲的消息
        while true do
            msg = sysplus.waitMsg(taskName, nil)
            if type(msg) == 'table' then
                if msg[1] == MSG_PD then
                    log.info("播放结束")
                    break
                end
            else
                log.error(type(msg), msg)
            end
        end
    else
        log.debug("解码失败!")
        sys.wait(1000)
    end

    -- 下面的演示是将音频文件发送到服务器上,如有需要,可以将下面代码注释打开,这里的url是合宙的文件上传测试服务器,上传的文件到http://tools.openluat.com/tools/device-upload-test查看
    --[[ 
        local timeTable = os.date("*t", os.time())
        local nowTime = string.format("%4d%02d%02d_%02d%02d%02d", timeTable.year, timeTable.month, timeTable.day, timeTable.hour, timeTable.min, timeTable.sec)
        local filename = mobile.imei() .. "_" .. nowTime .. ".amr"
        postMultipartFormData("http://tools.openluat.com/api/site/device_upload_file", filename, recordPath)
    ]]
    --该方法为从串口1,把录音数据传给串口1
    --  uart.setup(1, 115200)                               -- 开启串口1
    --  uart.write(1, io.readFile(recordPath))              -- 向串口发送录音文件

end

sysplus.taskInitEx(audio_task, taskName, task_cb)

-- 用户代码已结束---------------------------------------------
-- 结尾总是这一句
sys.run()
-- sys.run()之后后面不要加任何语句!!!!!

6.2 结果演示

七、总结

本文演示如何在 Air8000 整机板外挂 es8311,提供了两种录音方式,1:录音到文件,2:录音到内存,然后编码存储到文件。然后提供了两种扩展的应用方式 1:把录音文件发送到服务器,2:把录音文件发送到串口。

八、功耗数据

此功耗测试数据的场景为飞行模式下,单纯录音:录音功耗下载地址:点我,下载完整压缩文件包

九、常见问题

后续在此扩展补充,敬请期待......