加入性能统计
加入帧不存在自动跳帧 修改解码方式
This commit is contained in:
250
play.lua
250
play.lua
@@ -1,15 +1,23 @@
|
|||||||
-- 简化版视频播放器
|
-- 简化版视频播放器
|
||||||
local gpu = peripheral.wrap("tm_gpu_0")
|
local gpu = peripheral.wrap("tm_gpu_0")
|
||||||
|
if not gpu then gpu = peripheral.find("tm_gpu") end
|
||||||
gpu.refreshSize()
|
gpu.refreshSize()
|
||||||
gpu.setSize(64)
|
gpu.setSize(64)
|
||||||
local w, h = gpu.getSize()
|
local w, h = gpu.getSize()
|
||||||
|
local mypath = "/"..fs.getDir(shell.getRunningProgram())
|
||||||
|
if not fs.exists(mypath.."/speakerlib.lua") then shell.run("wget https://git.liulikeji.cn/xingluo/computer_craft_video_play/raw/branch/main/speakerlib.lua") end
|
||||||
|
|
||||||
if not fs.exists("speakerlib.lua") then shell.run("wget https://git.liulikeji.cn/xingluo/computer_craft_video_play/raw/branch/main/speakerlib.lua") end
|
local server_url = "https://newgmapi.liulikeji.cn"
|
||||||
|
|
||||||
server_url = "https://newgmapi.liulikeji.cn"
|
|
||||||
|
|
||||||
-- 检查命令行参数
|
-- 检查命令行参数
|
||||||
local videoUrl = ...
|
local age = {...}
|
||||||
|
local videoUrl = age[1]
|
||||||
|
local debug
|
||||||
|
|
||||||
|
if age[2] == "debug" then
|
||||||
|
debug = true
|
||||||
|
end
|
||||||
|
|
||||||
if not videoUrl then
|
if not videoUrl then
|
||||||
print("Usage: video_player <video URL>")
|
print("Usage: video_player <video URL>")
|
||||||
print("Example: video_player https://example.com/video.mp4")
|
print("Example: video_player https://example.com/video.mp4")
|
||||||
@@ -105,7 +113,7 @@ while true do
|
|||||||
|
|
||||||
-- 检查是否完成
|
-- 检查是否完成
|
||||||
if task_info.result.current_frames then
|
if task_info.result.current_frames then
|
||||||
if task_info.result.current_frames >= 200 then
|
if task_info.result.current_frames >= 400 then
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -180,7 +188,7 @@ end
|
|||||||
-- 分批下载(每批 50 帧)
|
-- 分批下载(每批 50 帧)
|
||||||
local BATCH_SIZE = 20
|
local BATCH_SIZE = 20
|
||||||
-- local totalFrames = #videoInfo.frame_urls
|
-- local totalFrames = #videoInfo.frame_urls
|
||||||
local totalFrames = videoInfo.fps * 10 -- 仅下载前10秒以节省时间
|
local totalFrames = videoInfo.fps * 20 -- 仅下载前10秒以节省时间
|
||||||
local allFrameData = {}
|
local allFrameData = {}
|
||||||
|
|
||||||
-- 第一步:构建所有需要下载的批次(仅前10秒)
|
-- 第一步:构建所有需要下载的批次(仅前10秒)
|
||||||
@@ -251,8 +259,6 @@ local starttime1 = os.clock()
|
|||||||
|
|
||||||
-- 播放前已缓存 10 秒
|
-- 播放前已缓存 10 秒
|
||||||
local totalFramesToPlay = #videoInfo.frame_urls
|
local totalFramesToPlay = #videoInfo.frame_urls
|
||||||
local maxCachedFrames = math.min(videoInfo.fps * 20, totalFramesToPlay) -- 最多缓存 20 秒
|
|
||||||
local nextDownloadIndex = math.min(videoInfo.fps * 10 + 1, totalFramesToPlay + 1) -- 从第 10 秒后开始继续下载
|
|
||||||
|
|
||||||
-- 标志:是否还在播放
|
-- 标志:是否还在播放
|
||||||
local running = true
|
local running = true
|
||||||
@@ -373,97 +379,199 @@ local function httpResponseHandler()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- === 全程性能记录表 (新增) ===
|
||||||
|
-- 结构: { {id=帧号, time=耗时秒}, ... }
|
||||||
|
local perf_log = {}
|
||||||
|
|
||||||
-- 视频渲染协程
|
-- 视频渲染协程
|
||||||
local function renderVideo()
|
local function renderVideo()
|
||||||
local frameIndex2 = 0
|
-- 基础参数初始化
|
||||||
local play_fps = videoInfo.fps
|
local fps = videoInfo.fps
|
||||||
|
local frameDelay = 1 / fps
|
||||||
|
local startTime = os.clock()
|
||||||
|
|
||||||
|
os.queueEvent("audio_start") -- 通知音频开始播放
|
||||||
|
|
||||||
|
-- === 性能统计变量 ===
|
||||||
|
local lastStatTime = os.clock() -- 上次结算时间
|
||||||
|
local frameCount = 0 -- 当前秒内已渲染帧数
|
||||||
|
local timeAccum = 0 -- 当前秒内累计渲染耗时
|
||||||
|
local maxFrameTime = 0 -- 当前秒内最大单帧耗时 (用于计算最低FPS)
|
||||||
|
|
||||||
|
-- === UI显示缓存 ===
|
||||||
|
local ui_avgFps, ui_avgMs = 0, 0
|
||||||
|
local ui_lowFps, ui_lowMs = 0, 0
|
||||||
|
|
||||||
|
local timer_id -- 声明计时器ID变量
|
||||||
|
|
||||||
local starttime = os.clock()
|
|
||||||
os.queueEvent("audio_start")
|
|
||||||
while running and frameIndex <= #videoInfo.frame_urls do
|
while running and frameIndex <= #videoInfo.frame_urls do
|
||||||
|
local frameStart = frameEnd
|
||||||
|
|
||||||
|
-- 1. 内存清理
|
||||||
local frame_start_time = os.clock()
|
for i = 1, math.max(0, frameIndex - 5) do
|
||||||
|
|
||||||
local data = allFrameData[frameIndex]
|
|
||||||
local imgBin = {}
|
|
||||||
for j = 1, #data do
|
|
||||||
imgBin[#imgBin + 1] = data:byte(j)
|
|
||||||
end
|
|
||||||
|
|
||||||
for i = 1, frameIndex - 5 do
|
|
||||||
allFrameData[i] = nil
|
allFrameData[i] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
data = nil
|
local data = allFrameData[frameIndex]
|
||||||
local success, image = pcall(function()
|
|
||||||
return gpu.decodeImage(table.unpack(imgBin))
|
|
||||||
end)
|
|
||||||
|
|
||||||
--local image = frames[frameIndex]
|
|
||||||
|
|
||||||
|
|
||||||
-- 播放当前帧
|
-- === 如果 data 存在才进行解码和渲染 ===
|
||||||
if image then
|
if data then
|
||||||
|
-- 将字符串转换为字节表
|
||||||
|
local imgBin = { data:byte(1, #data) }
|
||||||
|
data = nil
|
||||||
|
|
||||||
|
-- 2. 图像解码
|
||||||
|
local success, image = pcall(function()
|
||||||
|
return gpu.decodeImage(table.unpack(imgBin))
|
||||||
|
end)
|
||||||
|
|
||||||
gpu.drawImage(0, 0, image.ref())
|
if success and image then
|
||||||
--gpu.drawText(1,1,frameIndex .. " / "..#videoInfo.frame_urls.." fps: "..math.ceil(play_fps),0xffffff)
|
-- 3. 画面渲染
|
||||||
gpu.drawText(1,1,frameIndex .. " / "..#videoInfo.frame_urls.." fps: "..math.ceil(play_fps),0xffffff)
|
gpu.drawImage(0, 0, image.ref())
|
||||||
local event, id
|
|
||||||
if frameIndex > 1 then
|
if debug then
|
||||||
|
-- 格式:帧进度 | 平均FPS(平均耗时) | 最低FPS(波峰耗时)
|
||||||
|
local statusText = string.format("%d/%d Avg:%d(%dms) Low:%d(%dms)",
|
||||||
|
frameIndex,
|
||||||
|
#videoInfo.frame_urls,
|
||||||
|
math.floor(ui_avgFps),
|
||||||
|
math.floor(ui_avgMs),
|
||||||
|
math.floor(ui_lowFps),
|
||||||
|
math.floor(ui_lowMs)
|
||||||
|
)
|
||||||
|
gpu.drawText(1, 1, statusText, 0xFFFFFF)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 这里的 Timer 逻辑移出去了,确保跳帧时也能同步
|
||||||
|
|
||||||
|
if frameIndex > 1 and timer_id then
|
||||||
|
repeat
|
||||||
|
local event, id = os.pullEvent("timer")
|
||||||
|
until id == timer_id
|
||||||
|
end
|
||||||
|
|
||||||
|
timer_id = os.startTimer(frameDelay)
|
||||||
|
|
||||||
|
gpu.sync()
|
||||||
|
image:free()
|
||||||
|
-- === 修改点:只有成功渲染并同步后,才更新 frameEnd ===
|
||||||
|
frameEnd = os.clock()
|
||||||
|
else
|
||||||
|
print("Decode failed frame " .. frameIndex)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if frameIndex > 1 and timer_id then
|
||||||
repeat
|
repeat
|
||||||
event, id = os.pullEvent("timer")
|
local event, id = os.pullEvent("timer")
|
||||||
until id == timer_id
|
until id == timer_id
|
||||||
end
|
end
|
||||||
|
|
||||||
timer_id = os.startTimer(frameDelay)
|
timer_id = os.startTimer(frameDelay)
|
||||||
|
-- === 修改点:数据为空时,不做任何操作,直接进入下方的同步逻辑 ===
|
||||||
gpu.sync()
|
-- 此时 frameEnd 依然等于 frameStart,计算出的 costTime 为 0
|
||||||
|
|
||||||
-- for i = 1, frameIndex - 4 do
|
|
||||||
-- local image = frames[i]
|
|
||||||
-- image:free()
|
|
||||||
-- end
|
|
||||||
image:free()
|
|
||||||
else
|
|
||||||
error(frameIndex.. "Frame does not exist, possibly due to full memory or insufficient performance")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if frameEnd and frameStart then
|
||||||
|
-- 5. 性能数据计算
|
||||||
|
local costTime = frameEnd - frameStart -- 如果跳帧,这里是 0 秒
|
||||||
|
|
||||||
|
-- 记录当前帧的统计信息到总表中
|
||||||
|
table.insert(perf_log, { f = frameIndex, t = costTime })
|
||||||
|
|
||||||
|
if debug then
|
||||||
|
-- 实时统计累加
|
||||||
|
frameCount = frameCount + 1
|
||||||
|
timeAccum = timeAccum + costTime
|
||||||
|
if costTime > maxFrameTime then maxFrameTime = costTime end
|
||||||
|
|
||||||
|
-- 每隔 1秒 更新一次UI显示的数值
|
||||||
|
-- 注意:如果大量跳帧,timeDiff 可能会包含很多等待时间,这里的逻辑保持原样即可
|
||||||
|
local statEndCheck = os.clock()
|
||||||
|
local timeDiff = statEndCheck - lastStatTime
|
||||||
|
if timeDiff >= 1.0 then
|
||||||
|
ui_avgFps = frameCount / timeDiff
|
||||||
|
ui_avgMs = (timeAccum / frameCount) * 1000
|
||||||
|
|
||||||
|
ui_lowFps = (maxFrameTime > 0) and (1 / maxFrameTime) or ui_avgFps
|
||||||
|
ui_lowMs = maxFrameTime * 1000
|
||||||
|
|
||||||
|
-- 重置计数器
|
||||||
|
lastStatTime = statEndCheck
|
||||||
|
frameCount = 0
|
||||||
|
timeAccum = 0
|
||||||
|
maxFrameTime = 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 6. 自动追帧逻辑
|
||||||
|
local playTime = os.clock() - startTime
|
||||||
|
local targetFrame = math.ceil(playTime / frameDelay)
|
||||||
|
|
||||||
|
-- 如果当前帧和理论帧 相差3 则跳帧
|
||||||
local frame_end_time = os.clock()
|
if targetFrame > frameIndex + 3 or targetFrame < frameIndex - 2 then
|
||||||
|
frameIndex = targetFrame
|
||||||
local frame_time = frame_end_time - frame_start_time
|
|
||||||
|
|
||||||
play_fps = 1 / frame_time
|
|
||||||
|
|
||||||
local play_time = tonumber(string.format("%.3f",os.clock() - starttime))
|
|
||||||
|
|
||||||
frameIndex2 = math.ceil(play_time / frameDelay)
|
|
||||||
|
|
||||||
if frameIndex2 + (frameIndex - frameIndex2) * 0.5 >= 4 then
|
|
||||||
frameIndex = frameIndex2
|
|
||||||
else
|
else
|
||||||
frameIndex = frameIndex+1
|
frameIndex = frameIndex+1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- 启动协程
|
-- 启动协程
|
||||||
-- 启动三个协程:渲染 + 缓存调度 + HTTP 响应处理
|
-- 启动三个协程:渲染 + 缓存调度 + HTTP 响应处理
|
||||||
parallel.waitForAny(renderVideo, cacheAhead, httpResponseHandler)
|
parallel.waitForAny(renderVideo, cacheAhead, httpResponseHandler)
|
||||||
|
|
||||||
|
|
||||||
local endtime = os.clock()
|
local endtime = os.clock()
|
||||||
local time = endtime-starttime1
|
local total_time_sec = endtime - starttime1
|
||||||
|
|
||||||
|
-- 格式化总播放时间为 HH:MM:SS
|
||||||
|
local function format_seconds_to_hms(seconds)
|
||||||
|
local h = math.floor(seconds / 3600)
|
||||||
|
local m = math.floor((seconds % 3600) / 60)
|
||||||
|
local s = math.floor(seconds % 60)
|
||||||
|
return string.format("%02d:%02d:%02d", h, m, s)
|
||||||
|
end
|
||||||
|
|
||||||
print("Playback finished")
|
print("Playback finished")
|
||||||
print("Play_Time: ".. time)
|
print("Play_Time: " .. format_seconds_to_hms(total_time_sec))
|
||||||
local fps = #videoInfo.frame_urls / time
|
|
||||||
print("Final_FPS: "..fps)
|
|
||||||
|
|
||||||
print("Average Frame Interval: "..1/fps)
|
if #perf_log == 0 then
|
||||||
|
print("No performance data recorded.")
|
||||||
|
else
|
||||||
|
local total_frame_time = 0
|
||||||
|
local max_frame_time = 0
|
||||||
|
|
||||||
|
for _, entry in ipairs(perf_log) do
|
||||||
|
local t = entry.t
|
||||||
|
|
||||||
|
total_frame_time = total_frame_time + t
|
||||||
|
if t > max_frame_time then
|
||||||
|
max_frame_time = t
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local avg_frame_time = total_frame_time / #perf_log -- 单位:秒
|
||||||
|
local avg_fps = 1 / avg_frame_time
|
||||||
|
local min_fps = max_frame_time > 0 and (1 / max_frame_time) or 0
|
||||||
|
|
||||||
|
-- 转换为整数 FPS 和毫秒
|
||||||
|
local avg_fps_int = math.floor(avg_fps)
|
||||||
|
local min_fps_int = math.floor(min_fps)
|
||||||
|
local avg_ms = math.floor(avg_frame_time * 1000)
|
||||||
|
local max_ms = math.floor(max_frame_time * 1000)
|
||||||
|
|
||||||
|
print(string.format("Avg_FPS: %d (%dms)", avg_fps_int, avg_ms))
|
||||||
|
print(string.format("Low_FPS: %d (%dms)", min_fps_int, max_ms))
|
||||||
|
|
||||||
|
print("The ID for playing the video is "..task_id)
|
||||||
|
print("You can use the following command to replay this video within an hour.")
|
||||||
|
print('" play '..task_id..' "')
|
||||||
|
|
||||||
|
if debug then
|
||||||
|
local perf_log_file = fs.open(mypath.."/perf_log.json","w")
|
||||||
|
perf_log_file.write(textutils.serialiseJSON(perf_log))
|
||||||
|
print("perf_log has been saved to "..mypath.."/perf_log.json")
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user