Jasna 流式播放(Streaming)实战指南:边修复边看,HLS 浏览器播放与 Stash 集成

原创2026-10-21 02:27:051,892 阅读
文章标签:人工智能AI 应用计算机视觉视频处理图像处理深度学习

Jasna 流式播放(Streaming)实战指南:边修复边看,HLS 浏览器播放与 Stash 集成

Jasna 的 Streaming 模式让视频修复从"先跑完再看"变成"边修复边看":程序仍在后台对视频做画面修复(去码、超分等),你就能在浏览器里直接播放已经修复好的部分,并且支持任意拖拽跳转(Seeking)。本文以仓库中的 docs/en/streaming.md 为核心脉络,结合 jasna/streaming.py、jasna/streaming_pipeline.py、jasna/streaming_encoder.py 等源码与 tests/test_streaming.py 测试用例,完整讲解浏览器播放器的启动方式、全部 CLI 参数、底层 HLS 分片与按需生产机制,以及 Stash 场景下的集成步骤与排错方法。读完本文,你可以立即用一条命令开启实时修复预览,也能在自建 Stash 媒体库中直接调用 Jasna 的修复能力。

流式播放能做什么

传统修复流程是"输入视频 → 等待整个文件处理完毕 → 得到输出文件"。Jasna 的 Streaming 模式把这个过程变成了"边处理边交付":

  • 不需要等待整个文件:第一个分片(segment)处理完成后即可开始播放,后续内容在后台持续生产;
  • Seeking 可用:拖拽进度条到任意位置后,管线会自动中断当前生产任务,从目标位置重新开始一轮修复;
  • 不产生输出文件:处理结果通过 HTTP 以 HLS(HTTP Live Streaming)形式直接推送给浏览器,磁盘上只保留临时分片。

从 jasna/main.py 的 _run_streaming 可以看出,流式模式与普通输出模式完全并行:先启动 HLS 服务器,再进入"等待选片 → 运行修复管线 → 卸载视频"的循环。

浏览器播放器:一条命令开始

在命令行启动流式模式,Jasna 会自动打开内置的浏览器播放器页面:

jasna --stream

在 Windows 上使用与 GUI 相同的可执行文件:

jasna.exe --stream

启动后终端会打印两行信息(见 jasna/main.py):

HLS stream: http://localhost:8765/stream.m3u8
Browser:    http://localhost:8765/

浏览器页面会呈现一个极简播放器:在输入框中粘贴视频文件路径(或点击 Browse 使用系统文件对话框选择),回车即可开始播放。页面逻辑内置于 jasna/streaming.py 的 _PAGE_HTML 中,通过 hls.js 拉取 /stream.m3u8 播放列表并逐段加载 seg_NNNNN.ts 分片,右上角 Change Video 按钮可随时切换视频(对应 DELETE /open 请求)。

常用选项

选项 默认值 说明
--stream 关 启用 HLS 流式模式,不写输出文件(见 jasna/main.py)
--stream-port 8765 HLS HTTP 服务端口(jasna/main.py)
--stream-segment-duration 4.0 每个 HLS 分片的时长(秒),即管线每处理多少秒视频交付一个分片(jasna/main.py)
--no-browser 关 不自动打开浏览器,适合远程服务器或自行打开播放器的情况

如果你在远程主机或只想自己控制浏览器,可用:

jasna --stream --no-browser --stream-port 9000

然后手动访问 http://<主机>:9000/。

两种启动方式

  • 带输入启动:jasna --stream --input video.mp4,服务器启动后直接开始处理该视频;
  • 无输入启动:jasna --stream,服务器先就绪,播放器页面选好文件后(POST /open)才启动修复管线——这正是 Stash 集成所依赖的交互模式。

使用限制

--stream 与以下选项互斥,传入会直接报错(见 jasna/main.py):

  • --segments:分片/切片导出不能与流式组合;
  • --fmp4:分片 MP4 输出不适用于流式;
  • 文件夹输入与图片输入均不支持流式;
  • LTX 修复模型不支持流式(--restoration-model-name ltx ... does not support --stream);
  • --retarget-high-fps 同样被拒绝。

也就是说,流式模式固定使用 BasicVSR++ 修复模型与 H.264 编码,这一点在 docs/en/cli.md 帮助文本中也有明确注释("HLS streaming always uses H.264"),GUI 内置的 Video Player 同样遵循此规则(见 docs/en/gui.md)。

底层原理:按需生产的 HLS 服务器

浏览器播放器只是表象,真正支撑"边修复边看"的是仓库中的三层实现:HLS 服务器(jasna/streaming.py)、流式管线循环(jasna/streaming_pipeline.py)与流式编码器(jasna/streaming_encoder.py)。

HTTP 接口一览

HlsStreamingServer(jasna/streaming.py)基于 ThreadingMixIn + HTTPServer 实现,绑定 0.0.0.0,主要路由如下:

方法 路径 作用
GET / 返回播放器 HTML 页面
GET /status 查询当前是否正在流式播放及视频路径
GET /stream.m3u8 返回 VOD 型 HLS 播放列表,支持 ?start=秒数 从指定位置生成
GET /seg_NNNNN.ts 返回指定编号的视频分片(video/mp2t),带 ?epoch= 防过期播放
POST /api/browse 弹出系统文件选择对话框,返回所选路径
POST /open 选择视频文件开始流式播放(可带 start 指定起始时间)
DELETE /open 停止当前流,回到选片界面

所有响应都带 CORS 头(Access-Control-Allow-Origin: *),支持跨域代理访问。

播放列表与"防串台"机制

视频元数据加载后,服务器会调用 _generate_vod_playlist(jasna/streaming.py)一次性生成整段视频的 VOD 播放列表:按 segment_duration 把总时长切成 ceil(total / segment) 个分片,最后一个分片取余长,写入标准的 #EXTM3U / #EXT-X-VERSION:3 / #EXT-X-PLAYLIST-TYPE:VOD / #EXT-X-MEDIA-SEQUENCE / #EXTINF / #EXT-X-ENDLIST 结构。

这里有一个关键的工程细节——epoch 机制:每次发生 Seek 或切换视频时 playback_epoch 递增,播放列表里每个分片地址都会带上 ?epoch=N。当播放器还拿着旧时代的播放列表请求旧分片时,服务器校验 epoch 不匹配直接返回 404,从而避免"旧播放器请求重新触发新一轮 Seek 生产"的串台竞态。测试 tests/test_streaming.py(test_old_playback_cannot_restart_new_seek)正是验证这一行为。

分片按需生产与流量控制

修复管线不会无脑把整部视频处理完,而是以播放器的消费节奏为准:

  • 播放器每请求一个分片,服务器都会通过 notify_segment_requested 记录当前 _highest_requested_segment(玩家已请求的最高分片号),并用条件变量唤醒管线;
  • 管线在写入分片时调用 wait_for_demand(current_segment, max_ahead, ...)(jasna/streaming.py),若已生产分片超过"播放器已请求分片 + 3"(_MAX_SEGMENTS_AHEAD = 3,见 jasna/streaming_pipeline.py),就暂停生产,避免无谓的 GPU 空转与磁盘膨胀;
  • 同时用 _evict_old_segments 清理播放器身后过旧的分片(保留 2 * max_ahead = 6 个,jasna/streaming.py),并记录 _evicted_through,被驱逐的分片若再被请求会触发重新生产。

tests/test_streaming.py 的 TestDemandFlowControl 用例系统验证了这套机制:生产超前会阻塞、播放器追上来会唤醒、cancel 事件能解除阻塞(tests/test_streaming.py)。

Seek:中断当前 pass,从目标位置重启

Seek 由 jasna/streaming_pipeline.py 的 _streaming_loop 处理:

  1. 播放器请求某个未生产/已驱逐的分片时,服务器判定 needs_seek(向后退始终需要 Seek;向前跳超过 _FORWARD_SEEK_THRESHOLD = 5 个分片也需要,见 jasna/streaming.py 与 jasna/streaming.py);
  2. 服务器通过 request_seek 设置 seek_target_segment,当前正在跑的修复 pass 通过 poll 回调检测到后立即取消(_run_streaming_pass 中的 poll,jasna/streaming_pipeline.py);
  3. 流式编码器执行 flush_and_restart(start_number),丢弃未写完的分片、杀掉旧 ffmpeg、从新起点重启一个编码进程;
  4. 新 pass 从目标分片开始生产,期间带 -ss 跳转输入与 -output_ts_offset 保持时间戳连续(见 jasna/streaming_encoder.py)。

当整个视频都生产完毕(没有 Seek 请求)时,管线标记 mark_finished 并进入"空转等待"状态,直到用户发起新的 Seek 或切换视频(jasna/streaming_pipeline.py)。

编码器:GPU 硬件 H.264 + 音频直拷

StreamingEncoder(jasna/streaming_encoder.py)把修复管线输出的 RGB 帧通过管道喂给 ffmpeg 编码成 HLS MPEGTS 分片,按 GPU 厂商选择不同编码器:

  • NVIDIA:h264_nvenc,preset p4、tune ll(低延迟)、rc vbr、cq 19、bf 0(禁用 B 帧)、high profile、开启 spatial-aq/temporal-aq、rc-lookahead 8,并通过 -gpu 指定设备序号(多卡时取 device.index);
  • AMD:h264_amf,usage lowlatency_high_quality、quality balanced、rc qvbr、qvbr_quality_level 19、bf 0、high profile。

其他要点:

  • GOP 大小 = round(fps × segment_duration),保证每个分片自带关键帧、可独立解码;
  • 若源文件存在,会以 -map 1:v:0 -map 0:a:0? 复用音轨(-c:a copy 直拷),因此浏览器里能看到原声;
  • 非方形像素源会自动附加 setsar=宽/高 滤镜;
  • ffmpeg 必须可用(打包工具目录或 PATH 中),否则直接抛错提示;测试 tests/test_streaming.py 对 nvenc 参数、start_number、-ss、-output_ts_offset、音频直拷等命令构造均有断言。

在 Stash 中集成:场景即修即播

除了内置浏览器播放器,Jasna 还能以"外部修复引擎"的方式嵌入 Stash(开源媒体管理/播放器)流程:当你播放某个 scene 时,Stash 启动 Jasna 并实时提供修复后的画面。这需要配合一个定制版 Stash(Stash v0.30.1-jasna)使用。

集成步骤

  1. 下载定制版 Stash:获取 Stash v0.30.1-jasna 版本并解压;
  2. 设置环境变量:在启动 Stash 之前设置以下两个变量:
    • JASNA_CLI_PATH:jasna.exe 的完整路径;
    • JASNA_WORKING_DIR:jasna.exe 所在目录的完整路径;
  3. 先做一次"预热"运行(重要):用你将在 Stash 中使用的相同设置,在命令行对一段短视频手动执行一次流式播放。第一次运行会为你的 GPU 准备引擎/编译文件,耗时较长;如果跳过这一步,Stash 会在等待 Jasna 就绪时超时放弃;
  4. 启动 Stash 并播放场景:此后 Stash 会在播放时自动拉起 Jasna,视频被实时修复后再播放。

常见报错与排查

如果 Stash 日志出现:

timeout waiting for jasna-cli to become healthy

按以下顺序排查:

  1. 先检查 JASNA_CLI_PATH:确认路径完整、指向真实的 jasna.exe,且与 JASNA_WORKING_DIR 一致;
  2. 再做步骤 3 的预热运行:确保 GPU 引擎已就绪,避免首次运行耗时过长导致 Stash 判定超时。

与 GUI 内置 Video Player 的取舍

如果不依赖 Stash,也可以在 Jasna 图形界面中直接使用 Video Player(docs/en/gui.md):在窗口顶部点击 Video Player,选择检测模型、检测阈值及可选的次级修复(RTX Super Res 或 UNet 4x),选好视频后即可边修复边播放,同样不产生输出文件。GUI 播放器支持空格暂停/播放、左右方向键跳转 30 秒,并实时显示"已提前修复的秒数"。

两种方式的共同约束与差异:

  • 都固定使用 BasicVSR++,不使用 LTX;
  • CLI --stream 面向浏览器/HLS 与 Stash 集成,播放体验由 hls.js 驱动,适合远程/无人值守场景;
  • GUI Video Player 在应用内播放,且要求队列与 Restore preview 未在运行(见 docs/en/gui.md)。

质量保障:测试如何守护流式链路

流式功能并非"黑盒魔法",仓库用 tests/test_streaming.py(约 1479 行)从多个层面做了覆盖,可作为理解与验证的入口:

  • 播放列表生成:TestGenerateVodPlaylist 验证分片数量上取整、末分片时长、#EXT-X-ENDLIST 等结构;
  • 服务器状态机:TestHlsStreamingServer 验证视频加载、分片计数、Seek 请求/消费、多 Seek 后者胜出、视频切换触发事件等;
  • 流量控制:TestDemandFlowControl 验证超前暂停、玩家追赶快进、取消解阻塞、旧分片驱逐;
  • 编码器生命周期:TestStreamingEncoder* 用 mock 的 ffmpeg 进程断言命令构造(nvenc、SAR、start_number、音频直拷、-ss 与 -output_ts_offset),并覆盖 BrokenPipe、写线程竞态、超时强杀等异常路径;
  • HTTP 行为:TestHttp* 验证 CORS、manifest 裁剪(?start=40 从第 10 个分片开始)、过期 epoch 返回 404、分片等待播放列表定稿后才可读取等。

这些测试同时证明了流式模式对播放器节流、Seek 竞态、编码异常等场景都有明确处理策略,可作为二次开发或深入阅读的起点。

适用前提与小结

流式模式的核心前提是:GPU 修复速度接近或快于实时播放速度。如果播放器状态栏(GUI)或分片请求长期赶不上生产,说明硬件处理能力不足以支撑实时预览,此时应降低分辨率、减少修复负担或改用离线导出。另外,流式模式要求 ffmpeg 可用、首次运行需完成 GPU 引擎编译(这也是 Stash 场景必须"预热"的原因)。

一句话总结:jasna --stream 把"修复"和"观看"从串行变成并行,借助 HLS 分片、按需生产与 Seek 中断重启机制,实现了真正可用的边修复边看体验;而 Stash 集成则把这一能力延伸到媒体库播放流程中。相关实现与测试可分别查看 jasna/streaming.py、jasna/streaming_pipeline.py、jasna/streaming_encoder.py 与 tests/test_streaming.py。

登录后查看全文
jasna