go2rtc 的 Motion JPEG 全链路实战:MJPEG 拉流、推流、快照抓取与终端 ASCII 艺术流
2026/9/14 19:40:07 网站建设 项目流程

go2rtc 的 Motion JPEG 全链路实战:MJPEG 拉流、推流、快照抓取与终端 ASCII 艺术流

【免费下载链接】go2rtcUltimate camera streaming application项目地址: https://gitcode.com/GitHub_Trending/go/go2rtc

导读

本文围绕 go2rtc 内部的 internal/mjpeg/README.md 展开,系统讲解这个以“终极摄像头流媒体应用”为定位的项目中 MJPEG(Motion JPEG)模块的完整能力:如何把摄像头转换为同时含 H264 与 MJPEG 双编码的流、如何通过api/stream.mjpeg输出 MJPEG 流、如何用api/frame.jpeg抓取 JPEG 快照、如何把摄像头画面以 ASCII 艺术形式输出到服务器终端,以及如何反向把 MJPEG 流推入 go2rtc。读完本文,你将掌握 go2rtc 中 MJPEG 的“消费(拉取)—服务(输出)—摄取(输入)”三大场景的全部配置与 API 用法,并理解其背后的源码实现原理。


一、模块概览:一个模块,三种职责

从 internal/mjpeg/mjpeg.go 的Init()可以看到,该模块注册了四条核心 HTTP API 路由:

  • api/frame.jpeg—— 抓取 JPEG 快照(单帧)
  • api/stream.mjpeg—— 输出或接收 MJPEG 流(按 HTTP 方法区分)
  • api/stream.ascii—— 输出终端 ASCII 艺术流
  • api/stream.y4m—— 输出 YUV4MPEG 原始帧流

同时它还注册了 WebSocket 的mjpeg消息类型(ws.HandleFunc("mjpeg", handlerWS)),用于 Web UI 中直接在浏览器播放 MJPEG 画面。

对应官方文档 internal/mjpeg/README.md 的定位,本模块承担三种职责:

  1. 提供与接收 MJPEG 格式的流(双向);
  2. 负责接收 JPEG 格式的快照(单帧抓取);
  3. 支持以“动态 ASCII 艺术”格式把流输出到服务器控制台(终端)——这是 go2rtc 的特色功能之一,纯粹“图一乐”,但非常适合向朋友演示“无 GUI 也能看摄像头”。

值得一提:api/frame.jpegapi/stream.ascii两条路由都指向handlerStream之外的独立处理函数,其中frame.jpeghandlerKeyframe处理,而stream.asciistream.mjpeg共用handlerStream(根据 URL 后缀.mjpeg/.ascii分发到不同 Writer),见 internal/mjpeg/mjpeg.go。


二、MJPEG 客户端:如何让 go2rtc“拿到” MJPEG 流

关键前提:对于一个 MJPEG 格式的流,你的视频源必须包含 MJPEG 编码器(codec)。只要源里有 MJPEG 编码,就可以通过 API 接收MJPEG 流JPEG 快照

获取 MJPEG 流的常见途径有四种:

  1. 部分摄像头在 RTSP 流内直接提供 MJPEG 编码(例如大华 Dahua 摄像头的第二路流),相关协议见 internal/rtsp/README.md;
  2. 部分摄像头提供带 MJPEG 流的 HTTP 链接,协议支持见 internal/http/README.md;
  3. 部分摄像头提供 HTTP 快照链接,go2rtc 可以把快照序列转换为 MJPEG 流,同样见 internal/http/README.md;
  4. 通过 FFmpeg 集成把摄像头的 H264/H265 流转码为 MJPEG,集成说明见 internal/ffmpeg/README.md。

2.1 配置示例:一个流同时拥有 H264 与 MJPEG 两种编码

官方文档给出的标准配置如下,camera1同时列出两个源,go2rtc 会按顺序尝试,最终该流将同时具备 H264 和 MJPEG 两种编码:

streams: camera1: - rtsp://rtsp:12345678@192.168.1.123/av_stream/ch0 - ffmpeg:camera1#video=mjpeg

其中第一条直接对接摄像头的 RTSP 主码流(H264),第二条通过ffmpeg:前缀引用同名的camera1并指定#video=mjpeg进行转码。这样下游消费者(浏览器、ffplay、curl)就可以按需选择 H264 或 MJPEG。

2.2 客户端侧源码佐证:Consumer 如何消费 MJPEG

从 pkg/mjpeg/consumer.go 可以看到,MJPEG 消费者(NewConsumer())声明它只接受视频方向的两种编码:

  • JPEGcore.CodecJPEG
  • RAWcore.CodecRAW,原始 YUV 帧)

AddTrack中,若轨道编码是 RTP 封装的,则调用RTPDepay(RTP 解包);若是 RAW 原始帧,则调用Encoder在内存中把 YUV 帧直接编码为 JPEG(见 pkg/mjpeg/helpers.go,其内部通过jpeg.Encode完成,并支持跳过空帧)。这也是 go2rtc 能“无 FFmpeg 进程”直接消费 JPEG 流的原因——纯 Go 实现,零外部依赖。


三、MJPEG 服务端:四个输出 API

3.1 mpjpeg:输出 MJPEG 流

MJPEG 在 FFmpeg 生态里叫mpjpeg,因为它本质是携带 HTTP 头(multipart)的 JPEG 帧序列。go2rtc 提供标准输出接口:

ffplay http://192.168.1.123:1984/api/stream.mjpeg?src=camera1

用浏览器或 ffplay 打开即可看到实时画面。底层实现中,pkg/mjpeg/writer.go 会把Content-Type设置为multipart/x-mixed-replace; boundary=frame,这是浏览器 MJPEG 播放的标准 MIME 类型;每一帧都以--frame\r\nContent-Type: image/jpeg\r\nContent-Length: N的边界头封装后写出,并在写完后显式Flush()以保持实时性(见 pkg/mjpeg/writer.go)。

源码注释中保留了一条 Chrome 的历史 bug 记录(bugs.chromium.org/p/chromium/issues/detail?id=527446):Chrome 播放 MJPEG 时总是显示倒数第二帧,这也是代码中每帧都 flush 的原因之一。

3.2 jpeg:抓取 JPEG 快照

抓取单帧快照使用:

curl http://192.168.1.123:1984/api/frame.jpeg?src=camera1

返回的 HTTP 响应会携带Content-Type: image/jpegCache-Control: no-cache等头(见 internal/mjpeg/mjpeg.go),可直接保存或喂给其他系统。

支持的查询参数(均直接透传给 FFmpeg 转码):

参数别名取值与说明
widthw输出宽度,如width=640
heighth输出高度,如h=480
rotate90180270-90,旋转快照
hardwarehw硬件加速转码,详见项目 Wiki 的 Hardware-acceleration 章节
cache1m10s,获取缓存快照

参数解析逻辑在 internal/ffmpeg/jpeg.go 的parseQuery中:

  • width/height通过scale=W:H过滤器实现;
  • rotate通过transpose过滤器实现:90transpose=1(顺时针 90°),180→ 两次 transpose,-90/270transpose=2(逆时针 90°);
  • hardware交给hardware.MakeHardware生成对应硬解/硬编参数。

关于cache参数的三个重要行为(官方文档明确):

  1. 快照仅在请求携带cache参数时才会被缓存;
  2. 当缓存快照的“年龄”未超过cache指定的时间(如10s)时,直接复用缓存结果;
  3. cache参数不会校验缓存图片尺寸与请求中指定的尺寸是否一致——即第一次以某个分辨率请求后,后续即使换尺寸,只要在缓存窗口内仍会返回旧尺寸。

源码对应 internal/mjpeg/mjpeg.go:缓存以map[string]cacheEntry形式保存(键为src),命中条件为time.Since(entry.timestamp) < timeout

快照的底层原理handlerKeyframe使用magic.NewKeyframe()(pkg/magic/keyframe.go)注册为消费者。它会根据源编码分流处理:

  • 源为H264/H265:只取关键帧(h264.IsKeyframe/h265.IsKeyframe),解包成 Annex-B 裸流后交给ffmpeg.JPEGWithQuery调用 FFmpeg 转成 JPEG;
  • 源为JPEG:直接经mjpeg.FixJPEG修正 JPEG 头后返回;
  • 源为RAW:用mjpeg.Encoder编码为 JPEG。

其中FixJPEG(pkg/mjpeg/helpers.go)会处理各类“非标准” JPEG:对缺少 DHT 表的AVI1头 JPEG 注入默认 Huffman 表(InjectDHT),对头损坏的 JPEG 用 Go 标准库重新编码修复,确保快照能被下游(如 Telegram 服务器)正常识别。

3.3 ascii:把摄像头流输出到终端

这是 go2rtc 最具趣味性的功能:把 MJPEG 流转成动态 ASCII 艺术输出到服务器终端,无需任何 GUI 就能“看”摄像头。官方定位是“just for fun”——可以跟朋友炫耀“我能把摄像头流到服务器控制台”。

官方文档中有一段演示视频(YouTube 链接),其画面其实是该格式多种参数组合再加上音频的效果;需要说明的是,该格式本身不支持音频

使用要点(Tips)

  • 该功能只对 MJPEG 编码有效(必要时需先转码);
  • 建议选择低帧率(FPS)
  • 选择适合你终端大小的宽高
  • 不同终端支持的颜色数不同(8、256、rgb);
  • text参数需要URL 编码(如空格编码为%20);
  • 可以流式播放任意摄像头或磁盘上的文件。

go2rtc.yaml 配置示例—— 转码为 MJPEG、终端尺寸 210x59(16:9)、帧率 10:

streams: gamazda: ffmpeg:gamazda.mp4#video=mjpeg#hardware#width=210#height=59#raw=-r 10

其中#raw=-r 10把 FFmpeg 输出帧率限制为 10fps,#hardware启用硬件加速,width/height直接决定 ASCII 画布的字符网格大小。

API 参数

参数取值说明与示例
color空 /8/256/rgb/ SGR 序列前景色。8256为对应色数的自动映射,rgb为 24 位真彩;也可直接传 ANSI SGR 码,如30(黑)、37(白)、38;5;226(黄)
back同上背景色,如40(黑)、47(白)、48;5;226(黄)
text空 / 单字符 /block/ 亮度递增字符列表字符集。空为默认亮度渐变串;block使用块元素(░▒▓█家族);ox这类多字符列表按亮度升序映射

curl 示例(官方原文):

% curl "http://192.168.1.123:1984/api/stream.ascii?src=gamazda" % curl "http://192.168.1.123:1984/api/stream.ascii?src=gamazda&color=256" % curl "http://192.168.1.123:1984/api/stream.ascii?src=gamazda&back=256&text=%20" % curl "http://192.168.1.123:1984/api/stream.ascii?src=gamazda&back=8&text=%20%20" % curl "http://192.168.1.123:1984/api/stream.ascii?src=gamazda&text=helloworld"

实现原理(pkg/ascii/ascii.go):NewWriter收到每帧 JPEG 后先jpeg.Decode解码,再逐像素取 RGB,按亮度灰度映射到字符集,配合 ANSI 转义序列上色;开场发送\033[2J(清屏),每帧开头发送\033[H(光标归位)实现动画刷新。颜色映射细节:

  • color=8:在 8 色 xterm 调色板中搜索最接近的颜色(xterm256color(r,g,b,8));
  • color=256:映射到 256 色表(\033[38;5;Nm);
  • color=rgb:直接输出 24 位真彩(\033[38;2;R;G;Bm);
  • 默认text字符集为` .::--~~==++**##%%$@`(按亮度从暗到亮),block则映射到块元素" ░░▒▒▓▓█"(见 pkg/ascii/ascii.go)。

3.4 yuv4mpegpipe:输出原始 YUV 帧流

输出带 YUV4MPEG 头的原始 YUV 帧流

ffplay http://192.168.1.123:1984/api/stream.y4m?src=camera1

该接口由apiStreamY4M实现(internal/mjpeg/mjpeg.go),直接使用 pkg/y4m 的y4m.NewConsumer()作为消费者,把帧以 YUV4MPEG 容器格式原样输出。适合需要原始帧做进一步处理(如 AI 分析、自定义编码)的场景。


四、Streaming ingest:把 MJPEG 流推入 go2rtc

api/stream.mjpeg接口同时支持GET(输出)POST(输入),路由分发逻辑见 internal/mjpeg/mjpeg.go:r.Method != "POST"时走输出outputMjpeg,否则走输入inputMjpeg

官方给出的推流示例——用 FFmpeg 把本地视频转成 MJPEG 并推送到 go2rtc:

ffmpeg -re -i BigBuckBunny.mp4 -c mjpeg -f mpjpeg http://localhost:1984/api/stream.mjpeg?dst=camera1

关键点:

  • 使用-f mpjpeg输出格式(multipart JPEG,自带 HTTP 边界头);
  • 目标地址带dst=camera1查询参数,指定要推入的流名称;
  • go2rtc 侧inputMjpegmpjpeg.Open(r.Body)打开请求体作为 Producer,挂到streams.Get(dst)对应的流上(见 internal/mjpeg/mjpeg.go)。

底层 multipart 解析pkg/mpjpegNext(pkg/mpjpeg/multipart.go)逐行读取边界,解析 MIME 头中的Content-Length,按长度读满一帧 JPEG 体,再封装为带 90kHz 时间戳的 RTP 包写入接收者(pkg/mpjpeg/producer.go)。该 Producer 声明只接收JPEG编码(ClockRate 90000、RAW 负载类型),即推入的必须是 MJPEG 格式数据。

值得一提的是,multipart 解析中对某些实现拙劣的摄像头 MJPEG 服务器做了兼容处理(源码注释提到 Foscam G2 的 MJPEG 实现问题,见 pkg/mpjpeg/multipart.go),遇到连续--边界时会跳过空边界继续解析。


五、小结与使用建议

go2rtc 的 MJPEG 模块在 internal/mjpeg 提供 API 层、在 pkg/mjpeg、pkg/mpjpeg、pkg/ascii、pkg/y4m 提供编解码与容器实现,构成了完整的闭环:

  • 拉取:RTSP/HTTP 源里带 MJPEG 编码即可直接消费,H264/H265 源可用ffmpeg:#video=mjpeg转码;
  • 输出stream.mjpeg(浏览器/播放器)、frame.jpeg(快照)、stream.ascii(终端 ASCII 艺术)、stream.y4m(原始帧);
  • 摄取POST api/stream.mjpeg?dst=...接收外部 MJPEG 推流。

实际部署时建议:抓快照优先用cache参数降低反复转码开销(注意其不校验尺寸的语义);终端 ASCII 播放务必用低 FPS 并匹配终端尺寸;H264/H265 源抓 JPEG 快照依赖 FFmpeg 转码,需确保环境已正确配置 FFmpeg(见 internal/ffmpeg/README.md)。

【免费下载链接】go2rtcUltimate camera streaming application项目地址: https://gitcode.com/GitHub_Trending/go/go2rtc

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询