videoplayer 视频播放
一、概述
videoplayer 是 LuatOS 的视频播放核心库,负责把视频文件里的 MJPG(MJPEG)帧解码成 RGB565 像素,再送到屏幕显示,或者交给脚本自己处理。它只做“读帧 + 解码 + 送屏”,不涉及音频与播放列表。
要点:
1、模块名是 videoplayer,先 videoplayer.open(path) 拿到播放器对象,再循环调用 draw_frame 显示或 read_frame 取帧;
2、draw_frame 内部走显示抽象层:优先用 display 库,未启用时回退 lcd;两者都不可用会返回错误信息 no display;
3、read_frame 返回的 data 是 RGB565 原始像素的 Lua 字符串,长度 = width × height × 2。它已经拷贝到 Lua 侧,取帧后可以安全长期持有该字符串;
4、真正能放的格式只有 MJPG 裸流与 HZV(MJPEG + 音频)容器。FMT_AVI_MJPG 是预留枚举、FMT_MP4_H264 已不再解码,这两个常量只是数值占位,不要拿它们判断“能不能播”;
5、解码分软件与硬件两种模式:软件解码基于 tjpgd,通用但吃 CPU;硬件解码依赖平台实现,切换失败会返回 false。
⚠️ 该库需要固件在编译时打开
LUAT_USE_VIDEOPLAYER。如果脚本里videoplayer为 nil,说明当前固件没有包含该库,需要向对接 FAE 确认固件版本。
二、核心示例
-- 打开视频文件
local player, err = videoplayer.open("/sdcard/video.mjpg")
if not player then
log.error("videoplayer", "打开失败", err)
return
end
-- 先看视频参数
local info = videoplayer.info(player)
if info then
log.info("videoplayer", "分辨率", info.width, info.height,
"格式", info.format, "解码模式", info.decode_mode)
end
-- 逐帧播放到屏幕左上角
while true do
local ok, e = videoplayer.draw_frame(player, 0, 0)
if e == "eof" then
log.info("videoplayer", "播放结束")
break
elseif not ok then
log.error("videoplayer", "解码失败", e)
break
end
sys.wait(33) -- 约 30fps
end
videoplayer.close(player)
player = nil
三、常量详解
3.1 解码模式常量
| 常量 | 值 | 含义 |
|---|---|---|
videoplayer.DECODE_SW |
0 | 软件解码(tjpgd),通用,CPU 占用较高 |
videoplayer.DECODE_HW |
1 | 硬件解码,可用性取决于平台 |
3.2 视频格式常量
| 常量 | 值 | 含义 | 现状 |
|---|---|---|---|
videoplayer.FMT_MJPG |
0 | 裸 MJPG 流 | 支持 |
videoplayer.FMT_AVI_MJPG |
1 | AVI 容器 + MJPG | 预留,未实现 |
videoplayer.FMT_MP4_H264 |
2 | MP4 容器 + H.264 | 已不再解码,仅保留数值避免枚举错位 |
videoplayer.FMT_HZV |
3 | HZV 容器(MJPEG + 音频) | 支持,本库只取视频轨 |
这两组常量同时也是 videoplayer.info() 返回的 format 与 decode_mode 字段的取值。
四、函数详解
videoplayer.open(path)
打开视频文件并创建播放器对象。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
| path | string | 视频文件路径,如 /sdcard/video.mjpg、/sdcard/video.hzv,支持 MJPG 与 HZV |
返回值:成功返回播放器对象(userdata);失败返回 nil 和错误信息字符串。
local player, err = videoplayer.open("/sdcard/video.hzv")
if not player then
log.error("videoplayer", "打开失败", err)
return
end
注意事项:
1、必须在文件系统挂载成功之后再调用(fatfs 或 io 先挂好 SD 卡),否则路径解析不到会直接失败;
2、文件扩展名不决定格式,真正识别的是文件内容;把 H264 的 mp4 改名成 mjpg 依然打不开。
videoplayer.close(player)
关闭播放器并释放全部资源。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
| player | userdata | videoplayer.open() 返回的播放器对象 |
返回值:无。
videoplayer.close(player)
player = nil
注意事项:
1、重复调用是安全的,对象内部的上下文置空后直接返回;
2、对象被 Lua 回收时会自动再关一次,但工程上仍建议显式关闭,避免长时间占用解码缓冲区。
videoplayer.read_frame(player)
读取并解码下一帧,返回帧数据表。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
| player | userdata | videoplayer.open() 返回的播放器对象 |
返回值:
- 成功:table,字段见下表;
- 到达文件末尾:
nil和"eof"; - 出错:
nil和错误信息字符串(format/nomem/io/decode/param/error); - 播放器已关闭:
nil和"player closed"。
| 字段 | 类型 | 说明 |
|---|---|---|
| width | number | 帧宽(像素) |
| height | number | 帧高(像素) |
| data | string | RGB565 原始像素,长度 = width × height × 2 |
| pts | number | 显示时间戳,单位由 timescale 决定 |
| duration | number | 本帧时长,单位同 timescale |
| timescale | number | 时间基,pts / timescale 即秒 |
while true do
local frame, err = videoplayer.read_frame(player)
if err == "eof" then break end
if frame then
-- frame.data 已经是 RGB565,可直接送 lcd / 自己的图像处理
log.info("videoplayer", "帧", frame.width, frame.height, "#data", #frame.data)
end
end
注意事项:
1、每帧的 data 都是一块新字符串,1024×600 一帧约 1.2MB。循环播放时不要把所有帧攒进 table,内存很快会耗尽;
2、read_frame 与 draw_frame 都会消耗一帧,二者不要对同一次播放交叉调用,否则会丢帧。
videoplayer.draw_frame(player, x, y)
读取下一帧、解码并直接绘制到默认屏幕。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
| player | userdata | videoplayer.open() 返回的播放器对象 |
| x | int | 显示起始 X 坐标 |
| y | int | 显示起始 Y 坐标 |
返回值:成功返回 true;到达末尾返回 nil 和 "eof";失败返回 nil 和错误信息,其中没有可用显示设备时为 "no display"。
while true do
local ok, err = videoplayer.draw_frame(player, 40, 30)
if err == "eof" then break end
if not ok then
log.error("videoplayer", "绘制失败", err)
break
end
sys.wait(33)
end
注意事项:
1、坐标在库内部按 int16 处理,有效范围 -32768~32767,正常按屏幕坐标从 0 开始传即可;
2、绘制目标由固件决定:启用 display 库时走 display,否则回退 lcd。屏没初始化会得到 no display,先 lcd.init 再播放;
3、单帧解码耗时随分辨率变化,帧率要靠 sys.wait 自己控,库不做定时。
videoplayer.info(player)
获取视频信息。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
| player | userdata | videoplayer.open() 返回的播放器对象 |
返回值:成功返回 table(width、height、format、decode_mode);播放器无效或取信息失败返回 nil(不返回错误字符串)。
local info = videoplayer.info(player)
if info then
log.info("videoplayer", "分辨率", info.width, info.height)
log.info("videoplayer", "格式", info.format, "解码模式", info.decode_mode)
end
format 的取值见 3.2 节,decode_mode 见 3.1 节。
videoplayer.set_decode_mode(player, mode)
设置解码模式,播放过程中可以随时切换。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
| player | userdata | videoplayer.open() 返回的播放器对象 |
| mode | int | videoplayer.DECODE_SW 软件解码,videoplayer.DECODE_HW 硬件解码 |
返回值:成功返回 true,失败返回 false。
-- 先试硬件解码,失败就退回软件解码
if not videoplayer.set_decode_mode(player, videoplayer.DECODE_HW) then
log.info("videoplayer", "本平台不支持硬件解码,使用软件解码")
videoplayer.set_decode_mode(player, videoplayer.DECODE_SW)
end
注意事项:返回 false 的常见原因是播放器已关闭,或当前平台没有实现硬件解码。切到不支持的硬件解码后不会自动回退,要按返回值自己处理。
videoplayer.debug(on_off)
打开或关闭调试输出。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
| on_off | boolean | true 开启,false 关闭(默认关闭) |
返回值:无。
videoplayer.debug(true) -- 打印解码过程、帧大小等信息
videoplayer.debug(false) -- 关闭
五、产品支持说明
| 型号 | 文档中心是否有播放教程 | 教程入口 |
|---|---|---|
| Air1601 / Air1602 | 有 | /air1601/luatos/app/multimedia/ui/videoplayer/ |
| Air1603 | 有 | /air1603/luatos/app/multimedia/ui/videoplayer/ |
| Air780EPM / Air780EHM | 有 | /air780epm/luatos/app/multimedia/ui/videoplayer/ |
| Air780EGH | 有 | /air780egh/luatos/app/multimedia/ui/videoplayer/ |
| Air780EHV | 有 | /air780ehv/luatos/app/multimedia/ui/videoplayer/ |
| Air8000 | 有 | /air8000/luatos/app/multimedia/ui/videoplayer/ |
上表按文档中心已有的播放教程列出,不代表所有固件都包含该库。判断依据只有一条:脚本里 videoplayer 是否为 nil,为 nil 说明固件未编译该组件。