06 mjpg视频播放
作者:拓毅恒 | 最后修改:2026-07-30
一、视频播放功能概述
Air1601 工业引擎提供了视频播放功能,基于 AirUI 框架实现 MJPG 格式视频的流畅播放。视频播放系统支持以下主要功能:
-
本地视频播放:支持播放烧录到固件中的 MJPG 格式视频文件
-
网络视频播放:支持从 HTTP 服务器下载并播放视频
-
AirUI 视频组件:基于 airui.video 组件实现视频渲染
视频播放功能是嵌入式系统中多媒体应用的重要组成部分,掌握视频播放系统的使用方法对于实现视频展示、动态界面等应用至关重要。
二、准备硬件环境
参考:Air1601 硬件环境清单,准备好硬件环境。
2.1 Air1601 整机开发板 + LCD 屏幕
Air1601 开发板提供了丰富的显示接口资源,可通过开发板上的 LCD 接口连接显示屏进行视频播放测试。

三、准备软件环境
3.1 工具 + 内核固件 + 脚本
1、Luatools 下载调试工具
2、本 demo 开发测试时使用的固件为 Air1601 V1022 版本固件,所以你如果要测试本 demo 时,可以直接使用最新版本支持 AirUI 功能的内核固件;如果发现最新版本的内核固件测试有问题,可以使用我们开发本 demo 时使用的内核固件版本来对比测试;
3、luatos 需要的脚本和资源文件
-
脚本和资源文件点我浏览所有文件
-
准备好软件环境之后,接下来查看如何烧录项目文件到 Air1601 开发板中,将本篇文章中演示使用的项目文件烧录到Air1601开发板中。
4、lib 脚本文件:使用 Luatools 烧录时,勾选 添加默认 lib 选项,使用默认 lib 脚本文件;
3.2 API 介绍
这里仅介绍本篇文档所使用的 API,详情请查看:API - airui、API - 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_READY和IP_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"

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

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

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

5.2 网络视频播放功能演示
在 netdrv_device.lua中选择合适的网络驱动,默认使用WIFI联网

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

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

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

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

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

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