跳转至

06 mjpg视频播放

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

一、视频播放功能概述

Air1601 工业引擎提供了视频播放功能,基于 AirUI 框架实现 MJPG 格式视频的流畅播放。视频播放系统支持以下主要功能:

  1. 本地视频播放:支持播放烧录到固件中的 MJPG 格式视频文件

  2. 网络视频播放:支持从 HTTP 服务器下载并播放视频

  3. AirUI 视频组件:基于 airui.video 组件实现视频渲染

视频播放功能是嵌入式系统中多媒体应用的重要组成部分,掌握视频播放系统的使用方法对于实现视频展示、动态界面等应用至关重要。

二、准备硬件环境

参考:Air1601 硬件环境清单,准备好硬件环境。

2.1 Air1601 整机开发板 + LCD 屏幕

Air1601 开发板提供了丰富的显示接口资源,可通过开发板上的 LCD 接口连接显示屏进行视频播放测试。

image.png

三、准备软件环境

3.1 工具 + 内核固件 + 脚本

1、Luatools 下载调试工具

2、本 demo 开发测试时使用的固件为 Air1601 V1022 版本固件,所以你如果要测试本 demo 时,可以直接使用最新版本支持 AirUI 功能的内核固件;如果发现最新版本的内核固件测试有问题,可以使用我们开发本 demo 时使用的内核固件版本来对比测试;

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

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

3.2 API 介绍

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

airui.init(width, height)

初始化 AirUI 框架,设置显示区域尺寸

airui.container(config)

创建 AirUI 容器组件,用于布局和背景设置

airui.video(config)

创建视频播放组件,支持 MJPG 格式视频播放

lcd.init(driver, config)

初始化 LCD 显示屏驱动

lcd.getSize()

获取 LCD 屏幕的宽度和高度

四、视频播放功能实现概述

本小节详细介绍 Air1601 开发板上视频播放功能的实现方法和核心代码逻辑。

视频文件格式要求:

  • 格式:MJPG (Motion JPEG)

  • 帧率:默认 15fps,可通过 VIDEO_FPS 变量调整

4.1 本地视频播放功能

本地视频播放功能用于播放烧录到固件中的 MJPG 格式视频文件。

  • 自动播放 fly_man_80.mjpg 视频

  • 支持 MJPG 格式视频文件播放

  • 使用 AirUI 框架渲染视频

  • 视频居中显示,自动适应屏幕尺寸

4.1.1 功能定义

配置 LCD 和 AirUI 参数,加载并播放指定路径的视频文件,视频将在屏幕中央显示。

4.1.2 代码示例

--[[
本地视频播放功能模块
核心业务逻辑:
1. 初始化 LCD 和 AirUI 框架
2. 从 /luadb/fly_man_80.mjpg 加载视频
3. 使用 airui.video 组件播放视频
4. 视频居中显示,支持循环播放
]]

local VIDEO_PATH = "/luadb/fly_man_80.mjpg"    -- 视频文件路径
local VIDEO_FPS = 15                            -- 视频播放帧率

-- 使用 AirUI 播放视频
local function play_video_with_airui()
    log.info("播放器", "准备播放视频...")

    -- 检查视频文件是否存在
    if not io.exists(VIDEO_PATH) then
        log.error("播放器", "视频文件不存在:", VIDEO_PATH)
        return false
    end

    -- 获取 LCD 尺寸
    local lcd_width, lcd_height = lcd.getSize()
    log.info("播放器", "LCD尺寸:", lcd_width, "x", lcd_height)

    -- 获取视频实际分辨率
    local temp_player, err = videoplayer.open(VIDEO_PATH)
    local video_width, video_height = 160, 160
    if temp_player then
        local info = videoplayer.info(temp_player)
        if info then
            video_width = info.width
            video_height = info.height
            log.info("播放器", "视频实际分辨率:", video_width, "x", video_height)
        end
        videoplayer.close(temp_player)
    else
        log.warn("播放器", "无法获取视频信息,使用默认尺寸:", err)
    end

    -- 计算居中显示位置
    local x = math.floor((lcd_width - video_width) / 2)
    local y = math.floor((lcd_height - video_height) / 2)
    if x < 0 then x = 0 end
    if y < 0 then y = 0 end

    log.info("播放器", "视频显示位置:", x, y)

    -- 创建全屏黑色背景容器
    local screen_container = airui.container({
        x = 0,
        y = 0,
        w = lcd_width,
        h = lcd_height,
        color = 0x000000,
    })

    if not screen_container then
        log.error("播放器", "创建屏幕容器失败")
        return false
    end

    -- 创建视频容器
    local video_container = airui.container({
        parent = screen_container,
        x = x,
        y = y,
        w = video_width,
        h = video_height,
        color = 0x000000,
    })

    if not video_container then
        log.error("播放器", "创建视频容器失败")
        return false
    end

    -- 创建 airui.video 组件
    local interval = math.floor(1000 / VIDEO_FPS)
    local video_component = airui.video({
        parent = video_container,
        x = 0,
        y = 0,
        src = VIDEO_PATH,
        format = "mjpg",
        interval = interval,
        loop = true
    })

    if not video_component then
        log.error("播放器", "创建视频组件失败")
        return false
    end

    log.info("播放器", "视频组件创建成功")

    -- 开启背光
    gpio.setup(1, 1)
    log.info("播放器", "背光已开启")

    -- 开始播放
    video_component:play()
    log.info("播放器", "视频开始播放")

    -- 保持任务运行
    while true do
        sys.wait(1000)
    end
end

-- 播放器初始化任务
local function player_task()
    log.info("播放器", "初始化LCD和AirUI...")
    if not lcd_drv_init() then
        log.error("播放器", "LCD初始化失败")
        return
    end

    -- 开始播放视频
    play_video_with_airui()
end

-- 启动播放器任务
sys.taskInit(player_task)

4.2 网络视频播放功能

网络视频播放功能用于从 HTTP 服务器下载 MJPG 视频并播放。

4.2.1 功能定义

配置网络连接参数,从指定 URL 下载视频文件到本地,然后使用 AirUI 框架播放视频。

  • 从服务器下载视频文件

  • 支持 HTTP/HTTPS 协议

  • 下载完成后自动播放

  • 视频临时存储在 /ram/ 目录

4.2.2 代码示例

--[[
网络视频播放功能模块
核心业务逻辑:
1. 等待网络连接就绪
2. 从服务器下载视频到 /ram/server_video.mjpg
3. 使用 airui.video 组件播放视频
4. 支持循环播放
]]

local SERVER_VIDEO_URL = "https://appstoreoss.luatos.com/iot-apps/res/100197/video_160x160.mjpg"
local DOWNLOADED_VIDEO_PATH = "/ram/server_video.mjpg"
local VIDEO_FPS = 15

-- 从服务器下载视频
local function download_video()
    log.info("播放器", "从服务器下载视频:", SERVER_VIDEO_URL)

    -- 等待网络就绪
    log.info("播放器", "等待网络连接...")
    while not socket.adapter(socket.dft()) do
        sys.waitUntil("IP_READY", 1000)
    end
    log.info("播放器", "网络已就绪")

    -- 清理旧文件
    if io.exists(DOWNLOADED_VIDEO_PATH) then
        os.remove(DOWNLOADED_VIDEO_PATH)
        log.info("播放器", "删除旧视频文件")
    end

    -- 下载视频文件
    log.info("播放器", "开始下载...")
    local code, headers, body_size = http.request("GET", SERVER_VIDEO_URL, nil, nil,
        {dst = DOWNLOADED_VIDEO_PATH, timeout = 60000}).wait()

    if code ~= 200 then
        log.error("播放器", "下载失败, code:", code)
        return false
    end

    log.info("播放器", "下载完成, 大小:", body_size, "字节")

    -- 检查文件
    local actual_size = io.fileSize(DOWNLOADED_VIDEO_PATH)
    if actual_size ~= body_size then
        log.error("播放器", "文件大小不一致, 预期:", body_size, "实际:", actual_size)
        return false
    end

    return true
end

-- 使用 AirUI 播放视频
local function play_video_with_airui()
    log.info("播放器", "准备播放视频...")

    -- 下载视频
    if not download_video() then
        log.error("播放器", "下载视频失败")
        return false
    end

    -- 获取 LCD 尺寸
    local lcd_width, lcd_height = lcd.getSize()
    log.info("播放器", "LCD尺寸:", lcd_width, "x", lcd_height)

    -- 获取视频实际分辨率
    local temp_player, err = videoplayer.open(DOWNLOADED_VIDEO_PATH)
    local video_width, video_height = 160, 160
    if temp_player then
        local info = videoplayer.info(temp_player)
        if info then
            video_width = info.width
            video_height = info.height
            log.info("播放器", "视频实际分辨率:", video_width, "x", video_height)
        end
        videoplayer.close(temp_player)
    else
        log.warn("播放器", "无法获取视频信息,使用默认尺寸:", err)
    end

    -- 计算居中显示位置
    local x = math.floor((lcd_width - video_width) / 2)
    local y = math.floor((lcd_height - video_height) / 2)
    if x < 0 then x = 0 end
    if y < 0 then y = 0 end

    log.info("播放器", "视频显示位置:", x, y)

    -- 创建全屏黑色背景容器
    local screen_container = airui.container({
        x = 0,
        y = 0,
        w = lcd_width,
        h = lcd_height,
        color = 0x000000,
    })

    if not screen_container then
        log.error("播放器", "创建屏幕容器失败")
        return false
    end

    -- 创建视频容器
    local video_container = airui.container({
        parent = screen_container,
        x = x,
        y = y,
        w = video_width,
        h = video_height,
        color = 0x000000,
    })

    if not video_container then
        log.error("播放器", "创建视频容器失败")
        return false
    end

    -- 创建 airui.video 组件
    local interval = math.floor(1000 / VIDEO_FPS)
    local video_component = airui.video({
        parent = video_container,
        x = 0,
        y = 0,
        src = DOWNLOADED_VIDEO_PATH,
        format = "mjpg",
        interval = interval,
        loop = true
    })

    if not video_component then
        log.error("播放器", "创建视频组件失败")
        return false
    end

    log.info("播放器", "视频组件创建成功")

    -- 开启背光
    gpio.setup(1, 1)
    log.info("播放器", "背光已开启")

    -- 开始播放
    video_component:play()
    log.info("播放器", "视频开始播放")

    -- 保持任务运行
    while true do
        sys.wait(1000)
    end
end

-- 播放器初始化任务
local function player_task()
    log.info("播放器", "初始化LCD和AirUI...")
    if not lcd_drv_init() then
        log.error("播放器", "LCD初始化失败")
        return
    end

    -- 开始播放视频
    play_video_with_airui()
end

-- 启动播放器任务
sys.taskInit(player_task)

4.3 LCD 驱动初始化

LCD 驱动初始化模块负责配置 ST7796 LCD 显示屏和 AirUI 框架。

4.3.1 功能定义

初始化 LCD 驱动,配置屏幕参数,并初始化 AirUI 框架。

  • 初始化 ST7796 LCD 驱动

  • 配置 320x480 分辨率

  • 初始化 AirUI 框架

  • GPIO141 控制 LCD 供电使能

4.3.2 代码示例

--[[
LCD 驱动初始化模块
核心业务逻辑:
1. 使能 LCD 供电 LDO
2. 初始化 ST7796 LCD 驱动
3. 初始化 AirUI 框架
4. 背光在初始化完成后再开启
]]

-- LCD 初始化函数
function lcd_drv_init()
    -- Air1601 开发板上,使能 lcd 供电的 ldo 电源开关
    gpio.setup(141, 1)

    local result = lcd.init("st7796",
        {
            pin_pwr = nil,                          -- 背光控制引脚,先不开启
            port = lcd.HWID_0,                      -- 驱动端口
            pin_rst = 2,                            -- lcd 复位引脚
            direction = 0,                          -- lcd 屏幕方向
            w = 320,                                -- lcd 水平分辨率
            h = 480,                                -- lcd 竖直分辨率
            xoffset = 0,
            yoffset = 0,
            bus_speed = 16010000,                   -- SPI 总线速度
        })

    log.info("lcd.init", result)

    if result then
        -- 初始化 AirUI
        local width, height = lcd.getSize()
        local airui_result = airui.init(width, height)
        if not airui_result then
            log.error("airui", "init failed")
            return false
        end
        log.info("airui", "init success", width, height)
    end

    return result
end

4.4 主程序入口

主程序入口负责加载 LCD 驱动和视频播放功能模块。

4.4.1 功能定义

配置项目信息,加载必要的模块,选择播放模式(本地播放或网络播放)。

4.4.2 代码示例

--[[
主程序入口
核心业务逻辑:
1. 定义项目信息(PROJECT、VERSION)
2. 加载 LCD 驱动模块
3. 选择视频播放模式(二选一)
   - 本地视频播放:require "mjpg_player"
   - 网络视频播放:require "mjpg_player_server"
]]

PROJECT = "PLAY_MJPG"
VERSION = "001.999.000"

-- 在日志中打印项目名和项目版本号
log.info("main", PROJECT, VERSION)

-- 加载 LCD 驱动模块
require "lcd_drv"

-- 加载视频播放业务逻辑模块(二选一)
-- 场景一:从内存播放视频(默认启用)
require "mjpg_player"
-- 场景二:从服务器下载并播放视频
-- 测试从服务器下载播放功能,取消注释下一行
-- require "mjpg_player_server"

-- 用户代码已结束---------------------------------------------
sys.run()
-- sys.run()之后不要加任何语句!!!!!

4.5 网络驱动 (netdrv/)

网络驱动模块负责初始化和管理不同的网络连接方式,如 4G、WIFI 和以太网。

4.5.1 WIFI 网络驱动 (netdrv_wifi.lua)

  • 初始化 WIFI 模块,连接指定的热点(需要修改成需要连接的 WiFi 热点名称和密码,并且是 2.4G,不支持 5G WiFi)。

  • 通过将 wlan.connect(, , 1) 的第三个参数设置为 1 以开启自动重连功能。

  • 设置默认网卡为 socket.LWIP_STA

--[[
@module  netdrv_wifi
@summary "WIFI STA网卡"驱动模块
@version 1.0
@date    2025.07.01
@author  马梦阳
@usage
本文件为WIFI STA网卡驱动模块,核心业务逻辑为:
1、通过exnetif.set_priority_order配置airlink_wifi网卡;
   exnetif内部自动完成WiFi硬件初始化和airlink桥接初始化;
2、和WIFI路由器之间的连接状态发生变化时,在日志中进行打印;

本文件没有对外接口,直接在其他功能模块中require "netdrv_wifi"就可以加载运行;
]]

local exnetif = require "exnetif"

-- IP就绪回调
local function ip_ready_func(ip, adapter)
    if adapter == socket.LWIP_STA then
        -- 在位置1和2设置自定义的DNS服务器ip地址:
        -- "223.5.5.5",这个DNS服务器IP地址是阿里云提供的DNS服务器IP地址;
        -- "114.114.114.114",这个DNS服务器IP地址是国内通用的DNS服务器IP地址;
        -- 可以加上以下两行代码,在自动获取的DNS服务器工作不稳定的情况下,这两个新增的DNS服务器会使DNS服务更加稳定可靠;
        -- 如果使用专网卡,不要使用这两行代码;
        -- 如果使用国外的网络,不要使用这两行代码;
        socket.setDNS(adapter, 1, "223.5.5.5")
        socket.setDNS(adapter, 2, "114.114.114.114")

        log.info("netdrv_wifi.ip_ready_func", "IP_READY", socket.localIP(socket.LWIP_STA))
    end
end

-- IP丢失回调
local function ip_lose_func(adapter)
    if adapter == socket.LWIP_STA then
        log.warn("netdrv_wifi.ip_lose_func", "IP_LOSE")
    end
end

--WIFI联网成功(做为STATION成功连接AP,并且获取到了IP地址)后,内核固件会产生一个"IP_READY"消息
--各个功能模块可以订阅"IP_READY"消息实时处理WIFI联网成功的事件
--也可以在任何时刻调用socket.adapter(socket.LWIP_STA)来获取WIFI网络是否连接成功

--WIFI断网后,内核固件会产生一个"IP_LOSE"消息
--各个功能模块可以订阅"IP_LOSE"消息实时处理WIFI断网的事件
--也可以在任何时刻调用socket.adapter(socket.LWIP_STA)来获取WIFI网络是否连接成功

--此处订阅"IP_READY"和"IP_LOSE"两种消息
--在消息的处理函数中,仅仅打印了一些信息,便于实时观察WIFI的连接状态
--也可以根据自己的项目需求,在消息处理函数中增加自己的业务逻辑控制,例如可以在连网状态发生改变时更新网络图标
sys.subscribe("IP_READY", ip_ready_func)
sys.subscribe("IP_LOSE", ip_lose_func)

-- WiFi STA状态事件回调
local function wifi_sta_func(evt, data)
    -- evt 可能的值有: "CONNECTED", "DISCONNECTED"
    -- 当evt=CONNECTED, data是连接的AP的ssid, 字符串类型
    -- 当evt=DISCONNECTED, data断开的原因, 整数类型
    log.info("收到STA事件", evt, data)
end
sys.subscribe("WLAN_STA_INC", wifi_sta_func)

-- 初始化WiFi网卡任务
local function netdrv_wifi_task_func()
    -- 配置airlink WiFi单网卡,exnetif.set_priority_order使用的网卡编号为socket.LWIP_STA
    -- ssid为要连接的WiFi路由器名称;
    -- password为要连接的WiFi路由器密码;
    -- 注意:仅支持2.4G的WiFi,不支持5G的WiFi;
    -- 实际测试时,根据自己要连接的WiFi热点信息修改以下参数
    exnetif.set_priority_order({
        {
            airlink_wifi = {
                auto_socket_switch = false,
                airlink_type = airlink.MODE_UART,
                airlink_uart_id = 3,
                airlink_uart_baud = 2000000,
                ssid = "luatos1234",
                password = "12341234"
            }
        }
    })
end

-- 启动一个task,task的处理函数为netdrv_wifi_task_func
-- 在处理函数中调用exnetif.set_priority_order设置网卡优先级
-- 因为exnetif.set_priority_order要求必须在task中被调用,所以此处启动一个task
sys.taskInit(netdrv_wifi_task_func)

4.5.2 4G 网络驱动 (netdrv_4g.lua)

  • 监听 IP_READYIP_LOSE 消息,监控网络连接状态。

  • 设置默认网卡为 socket.LWIP_GP

```Plain Text --[[ @module netdrv_4g @summary "通过UART接口外挂4G模组(Air780EPM)的4G网卡"驱动模块 @version 1.0 @date 2026.4.15 @author 王城钧 @usage 本文件为 "通过UART接口外挂4G模组(Air780EPM)的4G网卡"驱动模块,核心业务逻辑为: 1、初始化和外部4G网卡的配置; 2、4G网卡的连接状态发生变化时,在日志中进行打印;

测试本功能模块时,Air780EPM需要烧录以下软件: 1、最新版本的内核固件(固件需支持airlink over uart功能) 2、脚本:https://gitee.com/openLuat/LuatOS/tree/master/module/Air1601_Air1602/demo/airlink/Air1601_780EPM_uart/Air780EPM

本文件没有对外接口,直接在其他功能模块中require "netdrv_4g"就可以加载运行; ]]

local exnetif = require "exnetif"

local function ip_ready_func(ip, adapter) if adapter == socket.LWIP_GP_GW then -- 在位置1和2设置自定义的DNS服务器ip地址: -- "223.5.5.5",这个DNS服务器IP地址是阿里云提供的DNS服务器IP地址; -- "114.114.114.114",这个DNS服务器IP地址是国内通用的DNS服务器IP地址; -- 可以加上以下两行代码,在自动获取的DNS服务器工作不稳定的情况下,这两个新增的DNS服务器会使DNS服务更加稳定可靠; -- 如果使用专网卡,不要使用这两行代码; -- 如果使用国外的网络,不要使用这两行代码; socket.setDNS(adapter, 1, "223.5.5.5") socket.setDNS(adapter, 2, "114.114.114.114")

    log.info("netdrv_4g.ip_ready_func", "IP_READY", socket.localIP(socket.LWIP_GP_GW))
end

end

local function ip_lose_func(adapter) if adapter == socket.LWIP_GP_GW then log.warn("netdrv_4g.ip_lose_func", "IP_LOSE") end end

-- 4G联网成功后,内核固件会产生一个"IP_READY"消息 -- 各个功能模块可以订阅"IP_READY"消息实时处理4G联网成功的事件 -- 也可以在任何时刻调用socket.adapter(socket.LWIP_GP_GW来获取4G是否联网成功

-- 4G断网后,内核固件会产生一个"IP_LOSE"消息 -- 各个功能模块可以订阅"IP_LOSE"消息实时处理4G断网的事件 -- 也可以在任何时刻调用socket.adapter(socket.LWIP_GP_GW)来获取4G是否联网成功

--此处订阅"IP_READY"和"IP_LOSE"两种消息 -- 在消息的处理函数中,仅仅打印了一些信息,便于实时观察4G的联网状态 -- 也可以根据自己的项目需求,在消息处理函数中增加自己的业务逻辑控制,例如可以在联网状态发生改变时更新网络图标 -- 此处订阅"IP_READY"和"IP_LOSE"两种消息 -- 在消息的处理函数中,仅仅打印了一些信息,便于实时观察"通过UART接口外挂4G模组(Air780EPM)的4G网卡"的连接状态 -- 也可以根据自己的项目需求,在消息处理函数中增加自己的业务逻辑控制,例如可以在连网状态发生改变时更新网络图标 sys.subscribe("IP_READY", ip_ready_func) sys.subscribe("IP_LOSE", ip_lose_func)

local function netdrv_4g_task_func() -- 配置UART外接的4G单网卡 -- 本demo使用Air1601核心板+Air780EPM核心板/开发板测试,Air1601核心板上的硬件配置为: -- 工作在UART模式 -- 使用uart3,波特率2000000 -- 如果使用的硬件和以上描述的环境不同,根据自己的硬件配置修改以下参数 exnetif.set_priority_order({ { -- 开启4G虚拟网卡 airlink_4G = { auto_socket_switch = false, -- 切换网卡时是否断开之前网卡的所有socket连接并用新的网卡重新建立连接 airlink_type = airlink.MODE_UART, -- airlink工作模式:UART模式 airlink_uart_id = 3, -- airlink使用的UART接口ID airlink_uart_baud = 2000000, -- airlink使用的UART波特率,默认2000000 airlink_adapter = socket.LWIP_GP_GW -- Air1601使用socket.LWIP_GP_GW网卡标识 } } }) end

-- 启动一个task,task的处理函数为netdrv_4g_task_func -- 在处理函数中调用exnetif.set_priority_order设置网卡优先级 -- 因为exnetif.set_priority_order要求必须在task中被调用,所以此处启动一个task sys.taskInit(netdrv_4g_task_func)

#### 4.5.3 SPI 以太网网络驱动(`netdrv_eth_spi.lua`)

- 通过 SPI 接口外挂 CH390H 芯片实现以太网。

- 通过控制 GPIO13 引脚使能芯片供电。

- 配置 SPI 接口参数,用于与 CH390H 芯片通信。

- 通过 `netdrv.setup` 函数配置以太网卡,并开启 DHCP 动态获取 IP 地址。

- 设置默认网卡为 `socket.LWIP_USER1`。

```Plain Text
--[[
@module  netdrv_eth_spi
@summary “通过SPI外挂CH390H芯片的以太网卡”驱动模块 
@version 1.0
@date    2025.07.24
@author  朱天华
@usage
本文件为“通过SPI外挂CH390H芯片的以太网卡”驱动模块 ,核心业务逻辑为:
1、打开CH390H芯片供电开关;
2、初始化spi1,初始化以太网卡,并且在以太网卡上开启DHCP(动态主机配置协议);
3、以太网卡的连接状态发生变化时,在日志中进行打印;

直接使用Air1601开发板硬件测试即可;

本文件没有对外接口,直接在其他功能模块中require "netdrv_eth_spi"就可以加载运行;
]]

local exnetif = require "exnetif"

local function ip_ready_func(ip, adapter)    
    if adapter == socket.LWIP_ETH then
        -- 在位置1和2设置自定义的DNS服务器ip地址:
        -- "223.5.5.5",这个DNS服务器IP地址是阿里云提供的DNS服务器IP地址;
        -- "114.114.114.114",这个DNS服务器IP地址是国内通用的DNS服务器IP地址;
        -- 可以加上以下两行代码,在自动获取的DNS服务器工作不稳定的情况下,这两个新增的DNS服务器会使DNS服务更加稳定可靠;
        -- 如果使用专网卡,不要使用这两行代码;
        -- 如果使用国外的网络,不要使用这两行代码;
        socket.setDNS(adapter, 1, "223.5.5.5")
        socket.setDNS(adapter, 2, "114.114.114.114")

        log.info("netdrv_eth_spi.ip_ready_func", "IP_READY", socket.localIP(socket.LWIP_ETH))
    end
end

local function ip_lose_func(adapter)    
    if adapter == socket.LWIP_ETH then
        log.warn("netdrv_eth_spi.ip_lose_func", "IP_LOSE")
    end
end

-- 以太网联网成功(成功连接路由器,并且获取到了IP地址)后,内核固件会产生一个"IP_READY"消息
-- 各个功能模块可以订阅"IP_READY"消息实时处理以太网联网成功的事件
-- 也可以在任何时刻调用socket.adapter(socket.LWIP_ETH)来获取以太网是否连接成功

-- 以太网断网后,内核固件会产生一个"IP_LOSE"消息
-- 各个功能模块可以订阅"IP_LOSE"消息实时处理以太网断网的事件
-- 也可以在任何时刻调用socket.adapter(socket.LWIP_ETH)来获取以太网是否连接成功

--此处订阅"IP_READY"和"IP_LOSE"两种消息
--在消息的处理函数中,仅仅打印了一些信息,便于实时观察“通过SPI外挂CH390H芯片的以太网卡”的连接状态
--也可以根据自己的项目需求,在消息处理函数中增加自己的业务逻辑控制,例如可以在连网状态发生改变时更新网络图标
sys.subscribe("IP_READY", ip_ready_func)
sys.subscribe("IP_LOSE", ip_lose_func)

local function netdrv_eth_spi_task_func()
    -- 配置SPI外接以太网芯片CH390H的单网卡,exnetif.set_priority_order使用的网卡编号为socket.LWIP_ETH
    -- 本demo使用Air1601开发板测试,开发板上的硬件配置为:
    -- VBAT为CH390H以太网芯片供电使能
    -- 使用spi1,片选引脚使用GPIO14,中断引脚使用GPIO51(WAKEUP)
    -- 如果使用的硬件不是Air1601开发板,根据自己的硬件配置修改以下参数
    exnetif.set_priority_order({
        {
            ETHERNET = {
                pwrpin = nil,
                tp = netdrv.CH390,
                opts = {spi = 1, cs = 14, irq = 51}
            }
        }
    })
end

-- 启动一个task,task的处理函数为netdrv_eth_spi_task_func
-- 在处理函数中调用exnetif.set_priority_order设置网卡优先级
-- 因为exnetif.set_priority_order要求必须在task中被调用,所以此处启动一个task
sys.taskInit(netdrv_eth_spi_task_func)

4.5.4 多网络驱动管理 (netdrv_multiple.lua)

  • 管理多个网络驱动实例,根据配置选择合适的网络连接方式。

  • 通过 exnetif.set_priority_order 函数配置多网卡的控制参数以及优先级。

  • 通过 exnetif.notify_status 函数设置网卡状态变化通知回调函数。

```Plain Text --[[ @module netdrv_multiple @summary 多网卡(通过SPI外挂CH390H芯片的以太网卡、AirLink 4G网卡、AirLink WiFi网卡)驱动模块 @version 1.0 @date 2025.07.24 @author 朱天华 @usage 本文件为多网卡驱动模块 ,核心业务逻辑为: 1、调用exnetif.set_priority_order配置多网卡的控制参数以及优先级; exnetif内部自动完成WiFi硬件初始化;

注意:airlink_4G和airlink_wifi都使用UART3,只能二选一开启。

直接使用Air1601开发板硬件测试即可;

本文件没有对外接口,直接在其他功能模块中require "netdrv_multiple"就可以加载运行; ]]

local exnetif = require "exnetif"

-- WiFi STA状态事件回调 local function wifi_sta_func(evt, data) -- evt 可能的值有: "CONNECTED", "DISCONNECTED" -- 当evt=CONNECTED, data是连接的AP的ssid, 字符串类型 -- 当evt=DISCONNECTED, data断开的原因, 整数类型 log.info("收到STA事件", evt, data) end sys.subscribe("WLAN_STA_INC", wifi_sta_func)

-- 网卡状态变化通知回调函数 -- 当exnetif中检测到网卡切换或者所有网卡都断网时,会触发调用此回调函数 -- 当网卡切换切换时: -- net_type:string类型,表示当前使用的网卡字符串 -- adapter:number类型,表示当前使用的网卡id -- 当所有网卡断网时: -- net_type:为nil -- adapter:number类型,为-1 local function netdrv_multiple_notify_cbfunc(net_type,adapter) -- 在位置1和2设置自定义的DNS服务器ip地址: -- "223.5.5.5",这个DNS服务器IP地址是阿里云提供的DNS服务器IP地址; -- "114.114.114.114",这个DNS服务器IP地址是国内通用的DNS服务器IP地址; -- 可以加上以下两行代码,在自动获取的DNS服务器工作不稳定的情况下,这两个新增的DNS服务器会使DNS服务更加稳定可靠; -- 如果使用专网卡,不要使用这两行代码; -- 如果使用国外的网络,不要使用这两行代码; socket.setDNS(adapter, 1, "223.5.5.5") socket.setDNS(adapter, 2, "114.114.114.114")

if type(net_type)=="string" then
    log.info("netdrv_multiple_notify_cbfunc", "use new adapter", net_type, adapter)
elseif type(net_type)=="nil" then
    log.warn("netdrv_multiple_notify_cbfunc", "no available adapter", net_type, adapter)
else
    log.warn("netdrv_multiple_notify_cbfunc", "unknown status", net_type, adapter)
end

end

local function netdrv_multiple_task_func() --设置网卡优先级 -- exnetif.set_priority_order内部会自动完成: -- - ETHERNET:SPI以太网初始化 -- - airlink_wifi:airlink_wifi_hardware_init(GPIO12/22/23)→ uart.setup → airlink → wlan.connect -- - airlink_4G:uart.setup → airlink → 4G拨号 exnetif.set_priority_order( { -- "通过SPI外挂CH390H芯片"的以太网卡,使用Air1601开发板验证 { ETHERNET = { -- VBAT给CH390H供电使能 -- 设置的多个"已经IP READY,但是还没有ping通"网卡,循环执行ping动作的间隔(单位毫秒,可选) -- 如果没有传入此参数,exnetif会使用默认值10秒 pwrpin = nil, ping_time = 3000,

                -- 连通性检测ip(选填参数);
                -- 如果没有传入ip地址,exnetif中会默认使用httpdns能否成功获取baidu.com的ip作为是否连通的判断条件;
                -- 如果传入,一定要传入可靠的并且可以ping通的ip地址;
                -- ping_ip = "填入可靠的并且可以ping通的ip地址",

                -- 网卡芯片型号(选填参数),仅spi方式外挂以太网时需要填写。
                tp = netdrv.CH390, 
                opts = {spi=1, cs=14, irq=51}
            }
        },

        -- AirLink WiFi网卡(Air1601 WHALE方案)
        -- 注意:airlink_wifi和airlink_4G都使用UART3,二者只能开启一个
        {
            airlink_wifi = {
                auto_socket_switch = false,     -- 切换网卡时是否断开之前网卡的所有socket连接并用新的网卡重新建立连接
                airlink_type = airlink.MODE_UART, -- airlink工作模式:UART模式
                airlink_uart_id = 3,            -- airlink使用的UART接口ID
                airlink_uart_baud = 2000000,    -- airlink使用的UART波特率,默认2000000
                ssid = "116",                   -- WiFi名称
                password = "hezhou666",      -- WiFi密码
            }
        },

        -- AirLink 4G网卡(Air1601外挂Air780EPM)
        -- 注意:airlink_4G和airlink_wifi都使用UART3,二者只能开启一个
        -- {
        --     airlink_4G = {
        --         auto_socket_switch = false,     -- 切换网卡时是否断开之前网卡的所有socket连接并用新的网卡重新建立连接
        --         airlink_type = airlink.MODE_UART, -- airlink工作模式:UART模式
        --         airlink_uart_id = 3,            -- airlink使用的UART接口ID
        --         airlink_uart_baud = 2000000,    -- airlink使用的UART波特率,默认2000000
        --         airlink_adapter = socket.LWIP_GP_GW -- Air1601使用socket.LWIP_GP_GW网卡标识
        --     }
        -- }
    }
)

end

-- 设置网卡状态变化通知回调函数netdrv_multiple_notify_cbfunc exnetif.notify_status(netdrv_multiple_notify_cbfunc)

-- 如果存在udp网络应用,并且udp网络应用中,根据应用层的心跳能够判断出来udp数据通信出现了异常; -- 可以在判断出现异常的位置,调用一次exnetif.check_network_status()接口,强制对当前正式使用的网卡进行一次连通性检测; -- 如果存在tcp网络应用,不需要用户调用exnetif.check_network_status()接口去控制,exnetif会在tcp网络应用通信异常时自动对当前使用的网卡进行连通性检测。

-- 启动一个task,task的处理函数为netdrv_multiple_task_func -- 在处理函数中调用exnetif.set_priority_order设置网卡优先级 -- 因为exnetif.set_priority_order要求必须在task中被调用,所以此处启动一个task sys.taskInit(netdrv_multiple_task_func)

```

五、功能演示

5.1 本地视频播放功能演示

确保 main.lua 中保留 require "mjpg_player" 语句,注释掉 require "mjpg_player_server"

image.png

使用 Luatools 将代码和视频文件烧录到 Air1601 开发板

image.png

烧录完毕后,日志中会打印视频播放的开始信息

image.png

开发板将自动播放本地视频文件,LCD 屏幕显示视频内容

3a9cd5644f8f8800ec.gif

5.2 网络视频播放功能演示

netdrv_device.lua中选择合适的网络驱动,默认使用WIFI联网

image.png

使用WIFI网卡连接参数,需要修改相应的WIFI账号密码

image.png

配置完毕后,确保 main.lua 中保留 require "mjpg_player_server" 语句,注释掉 require "mjpg_player"

image.png

使用 Luatools 将代码烧录到 Air1601 开发板

image.png

开发板将连接网络并下载视频

image.png

日志中会打印下载和播放状态

image.png

视频下载完成后自动播放,LCD 屏幕显示视频内容

03c2ed0bd0bd3b3a.gif

搜索
AirMaster 实时解答