跳转至

13 mjpg视频播放

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

一、视频播放功能概述

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

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

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

二、准备硬件环境

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

2.1 Air8000 整机开发板 + LCD 屏幕

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

2.2 Air8000 核心板 + AirLCD_1010 LCD 配件板

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

Air8000核心板
AirLCD_1010配件板
LCD_CLK
SCLK/CLK
LCD_CS
CS
LCD_RST
RES/RST
LCD_SDA
SDA/MOS
LCD_RS
DC/RS
GPIO1
BLK
VBAT
VCC
I2C1_SCL
SCL
I2C1_SDA
SDA
WAKEUP0
INT
GND
GND

Air8000购买链接:Air8000_4G/WiFi/以太网-上海合宙LuatOS官方企业店-淘宝网

三、准备软件环境

3.1 工具 + 内核固件 + 脚本

1、Luatools 下载调试工具

2、本 demo 开发测试时使用的固件为 Air8000 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 屏幕的宽度和高度

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

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

视频文件格式要求:

  • 格式:MJPG (Motion JPEG)
  • 分辨率:不超过 320x480(Air8000 LCD 分辨率)
  • 帧率:默认 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()
    -- Air8000 开发板上,使能 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 = 80000000,                   -- 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()之后不要加任何语句!!!!!

五、功能演示

5.1 本地视频播放功能演示

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

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

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

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

5.2 网络视频播放功能演示

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

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

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

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