跳转至

11 mjpg视频播放

作者:拓毅恒 | 最后修改:2026-06-09

一、视频播放功能概述

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

  1. 本地视频播放:支持播放烧录到固件中的 MJPG 格式视频文件
  2. 网络视频播放:支持从 HTTP 服务器下载并播放视频
  3. AirUI 视频组件:基于 airui.video 组件实现视频渲染
  4. LCD 显示支持:适配 320x480 分辨率 LCD 屏幕

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

二、准备硬件环境

参考:Air780EHM 硬件环境清单,准备好硬件环境。以下两种环境,任选一种即可。

2.1 Air780EHM 整机开发板 + LCD 屏幕

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

2.2 Air780EHM 核心板 + AirLCD_1010 LCD 配件板

Air780EHM 核心板和 AirLCD_1010 配件板的硬件接线方式为:

Air780EHM/Air780EHV/Air780EGH核心板
AirLCD_1010配件板
53/LCD_CLK
SCLK/CLK
52/LCD_CS
CS
49/LCD_RST
RES/RST
50/LCD_SDA
SDA/MOS
51/LCD_RS
DC/RS
22/GPIO1
BLK
3V3
VCC
67/I2C1_SCL
SCL
66/I2C1_SDA
SDA
20/GPIO24
INT
GND
GND

Air780EPM核心板购买链接:合宙Air780EPM核心板 所有引脚全引出 原理图全开放-淘宝网

Air780EHM核心板购买链接:合宙Air780EHM核心板 所有引脚全引出 原理图全开放-淘宝网

三、准备软件环境

3.1 工具 + 内核固件 + 脚本

1、Luatools 下载调试工具 Luatools

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

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

4、合宙 LuatIO 工具(GPIO 复用初始化配置)使用说明

5、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 屏幕的宽度和高度

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

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

视频文件格式要求:

  • 格式:MJPG (Motion JPEG)
  • 分辨率:不超过 320x480(Air780EHM LCD 分辨率)
  • 帧率:默认 15fps,可通过 VIDEO_FPS 变量调整

4.1 本地视频播放功能

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

  • 自动播放 fly_man_80.mjpg 视频
  • 支持 MJPG 格式视频文件播放
  • 使用 AirUI 框架渲染视频
  • 视频居中显示,自动适应屏幕尺寸

4.1.1 功能定义

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

4.1.2 代码示例

--[[
@module  mjpg_player
@summary MJPG视频播放器
@version 1.0.0
@date    2026.05.19
@author  拓毅恒
@usage
本模块使用AirUI框架实现MJPG视频播放功能:
1. 从 /luadb/fly_man_80.mjpg 加载视频
2. 使用 airui.video 组件播放视频
3. 支持循环播放

使用方法:
1. 将视频文件烧录到固件中
2. 在 main.lua 中 require "mjpg_player"
3. 上电后自动开始播放

注意事项:
1. 视频文件路径:/luadb/fly_man_80.mjpg
2. 视频分辨率不超过320x480
]]

-- ====================== 配置区域 ======================

-- 视频文件路径
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 代码示例

--[[
@module  mjpg_player_server
@summary MJPG视频播放器 - 服务器下载播放模式
@version 1.0.0
@date    2026.05.19
@author  拓毅恒
@usage
本模块实现从HTTP服务器下载MJPG视频并播放的功能:
1. 从服务器下载视频到 /ram/server_video.mjpg
2. 使用 airui.video 组件播放视频
3. 支持循环播放

使用方法:
1. 需要插入SIM卡并连接网络
2. 在 main.lua 中 require "mjpg_player_server"
3. 上电后自动下载并播放

注意事项:
1. 服务器视频URL:https://appstoreoss.luatos.com/iot-apps/res/100197/video_160x160.mjpg
2. 视频保存路径:/ram/server_video.mjpg
3. 视频分辨率不超过320x480
]]

-- ====================== 配置区域 ======================

-- 服务器视频URL
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 代码示例

--[[
@module  lcd_drv
@summary LCD驱动初始化模块
@version 1.0.0
@date    2026.05.19
@author  拓毅恒
@usage
本模块负责LCD显示屏的初始化和AirUI框架初始化:
1. 初始化 ST7796 LCD 驱动
2. 配置 320x480 分辨率
3. 初始化 AirUI 框架

使用方式:
1. 在 main.lua 中 require "lcd_drv"
2. 调用 lcd_drv_init() 初始化LCD

注意事项:
1. 背光在初始化完成后再开启,避免白屏
2. GPIO36 控制LCD复位
3. GPIO1 控制背光
]]

-- ====================== LCD驱动初始化 ======================

-- LCD初始化函数
function lcd_drv_init()
    local result = lcd.init("st7796",
        {
            pin_rst = 36,                           -- 复位引脚
            pin_pwr = nil,                          -- 背光控制引脚,先不开启
            port = lcd.HWID_0,                      -- 驱动端口
            direction = 0,                          -- lcd屏幕方向
            w = 320,                                -- lcd 水平分辨率
            h = 480,                                -- lcd 竖直分辨率
            xoffset = 0,
            yoffset = 0,
            sleepcmd = 0X10,
            wakecmd = 0X11,
        })

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

    if result then
        -- 显示设置
        lcd.setupBuff(nil, true)
        lcd.autoFlush(false)

        -- 初始化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 代码示例

--[[
@module  main
@summary LuatOS mjpg视频播放应用主入口,负责加载功能模块
@version 1.0.0
@date    2026.05.19
@author  拓毅恒
@usage
本demo演示两种视频播放场景(二选一):

场景一:播放本地烧录的视频(默认启用)
- 从 /luadb/fly_man_80.mjpg 加载并播放
- 需要将视频文件烧录到固件中

场景二:播放从服务器下载的视频
- 需要插入SIM卡并连接网络
- 从IOT云平台服务器下载视频后播放

使用说明:
根据需求启用对应的播放模式,注释掉不需要的模式
- 播放本地烧录的视频:  require "mjpg_player"
- 播放从服务器下载的视频:require "mjpg_player_server"

更多说明参考本目录下的readme.md文件
]]


--[[
必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
PROJECT:项目名,ascii string类型
        可以随便定义,只要不使用,就行
VERSION:项目版本号,ascii string类型
        如果使用合宙iot.openluat.com进行远程升级,必须按照"XXX.YYY.ZZZ"三段格式定义:
            X、Y、Z各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
            因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为999
        如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
]]
PROJECT = "PLAY_MJPG"
VERSION = "001.999.000"

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




-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
-- 启动errDump日志存储并且上传功能,600秒上传一次
-- if errDump then
--     errDump.config(true, 600)
-- end


-- 使用LuatOS开发的任何一个项目,都强烈建议使用远程升级FOTA功能
-- 可以使用合宙的iot.openluat.com平台进行远程升级
-- 也可以使用客户自己搭建的平台进行远程升级
-- 远程升级的详细用法,可以参考fota的demo进行使用



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

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

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

五、功能演示

5.1 本地视频播放功能演示

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

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

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

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

5.2 网络视频播放功能演示

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

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

开发板将连接网络并下载视频,日志中会打印下载和播放状态

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