# GoWVP Owl: a GB28181 video platform written in Go

> Owl is a Go implementation of a GB28181 network video platform, with ONVIF, RTMP and RTSP support, and its most interesting feature is on-demand preview transcoding that starts FFmpeg only when a browser cannot play the source codec, shares one process between viewers of the same camera, and reaps it when the last viewer leaves.

**gowvp/owl** — GB28181 - 2022 NVR | ONVIF/RTMP/RTSP | ZLMediaKit | 本地 YOLO 检测 | 私有部署

- Repository: https://github.com/gowvp/owl
- Website: http://gowvp.golang.space:15123
- Stars: 819 · Forks: 167
- Language: Go
- License: NOASSERTION
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/gowvp-owl

## A GB28181 platform, with the other protocols bridged in

GoWVP is an open-source GB28181 solution implemented in Go, a network video platform based on the GB28181-2022 standard that also supports the 2016 and 2011 versions, with ONVIF, RTMP and RTSP protocol support. The repository is named Owl, and the live demo is at the address in the project metadata.

GB28181 is the Chinese national standard for network video surveillance, and it is a SIP-based signalling protocol that national video platforms have to speak. Implementing it is the bulk of the work, and the fact that one codebase handles three revisions of the standard is the first thing to know about it.

The use cases are listed plainly. Browser-based camera video playback without plugins, which is the hard part, because the standard's media path is designed for a player on the local network rather than for a browser tab. Support for GB28181-compliant devices, which covers IP cameras, platforms and NVRs. Support for non-GB28181 devices over RTSP, RTMP and other streaming protocols, described as a way to maximise your existing equipment. Cross-network video preview. And deployment via Docker, Docker Compose or Kubernetes.

That combination is why the project exists in the form it does. A camera that speaks only RTSP and a camera that speaks GB28181 end up behind the same browser interface, and the media server in between handles the protocols the browser cannot.

The project is Go, the default branch is main, the last push was on 2026-10-14, and the releases are v1.3.0 in January 2026, v1.3.34 in May and v1.4.21 in August. The licence metadata records a custom licence, and the README explains the terms further down.

## Transcoding starts only when the browser cannot play the source

The on-demand preview transcoding feature is the most carefully specified part of the README, and the specification is the interesting thing.

WebRTC previews on the default ZLMediaKit node can transcode on demand. After validating the original channel's play token, Owl checks the browser SDP and the source codec. FFmpeg starts only for an H.265 source when the browser offers H.264 without H.265, and the result is H.264 Baseline video and Opus audio. H.264 sources and H.265-capable browsers use the original stream. And no frontend or camera codec changes are required.

Read that as a capability negotiation. The browser tells the server what it can decode, in the SDP, and the server decides whether to hand over the original stream or to convert. The conversion is the expensive path, so it is taken only when it is needed, and the trigger condition is narrow: the source is H.265 and the browser will not take H.265.

The rest of the section is about keeping that path from becoming a resource problem, which is where the settings come in. The concurrency limit counts cameras, not viewers, so ten people watching one camera cost one transcoder, and the limit is expressed per camera precisely because the cost is per camera. The recommendation is to adjust it to the server's CPU capacity and the source resolution and frame rate.

The feature is scoped to live WebRTC previews on the default ZLM node. Other protocols, recordings, lalmax and remote nodes retain their existing behaviour, which is stated plainly, and that scoping is what makes the feature safe to enable.

## One process per camera, reaped when the last viewer leaves

The resource story is described in a paragraph that reads like a post-mortem of a production issue, which is a good sign.

Viewers of the same camera share one process. Owl checks the media server's actual reader count every five seconds and stops and reaps the process after approximately thirty seconds without viewers, including failed ICE connections and abandoned requests. A reader-count query failure keeps the shared process alive and resets the idle grace period, and an unknown reader count is never treated as zero. Startup failures release their slots, and a graceful Owl shutdown stops all transcoders. Initial playback waits for the source and the transcoder to become ready, with a retry if the browser times out early.

Three of those rules are about not killing something that is in use. Treating an unknown reader count as zero would reap a live stream whenever the counting query failed, which is exactly the sort of default that turns a monitoring blip into an outage. Counting failed ICE connections and abandoned requests as viewers would do the opposite, keeping a process alive for nobody. So the grace period counts successful readers, ignores failed ones, and refuses to act on an unknown value.

The five-second polling interval is the other parameter worth noting, since it sets both how quickly an idle process dies and how long it survives a viewer who has not fully connected yet.

## The owl_preview app is reserved, and legacy channels can break it

Derived streams, in other words the transcoded previews, are not identified by a channel name alone. They use the reserved owl_preview app with a per-job internal key, and the rules around that are strict.

New channels cannot use this app, and existing channels cannot be moved into it. On startup, if an existing channel uses this app or the conflict check fails, Owl logs a warning and disables automatic preview transcoding while preserving normal channel authentication, playback and recording. The fix is to rename legacy channels to another app and restart Owl to enable transcoding. The PreviewDisabled setting also preserves the original callback behavior, and existing legacy channels can still be edited with their unchanged app.

There is a second consequence. These derived streams are excluded from recording and from the original-channel recording and AI callbacks, and original recordings retain the camera's codec.

The reason for all of this is that the reserved app is how the system recognises a derived stream and keeps it separate from a real camera channel. A camera configured with the name owl_preview would look like a transcoded preview, and the platform would then either transcode it twice or, in the case of the conflict check failing, quietly switch the feature off for the whole instance. The behaviour of choosing availability over the feature is the right trade for a surveillance system, since a platform that stops playing video is worse than one that stops optimising it, and the README says so in those terms.

## The configuration block and what the defaults do

The settings go into the existing Media section of the TOML configuration file, and then the process is restarted:

```toml
PreviewDisabled = false
PreviewFFmpeg = 'ffmpeg'
PreviewIdleSeconds = 30
PreviewMaxConcurrent = 3
```

Omitted fields enable the feature with the same defaults, which is a helpful convention, because a configuration file that predates the feature does not silently switch it off. An empty binary path resolves ffmpeg from the path, and zero idle and concurrency settings mean thirty seconds and three cameras. FFmpeg must provide libx264 and libopus, and Owl must be able to reach the media server's RTSP and HTTP API ports. The existing ZLMediaKit Docker image includes FFmpeg, while standalone binaries need it installed separately.

Two performance limits are stated. Transcoding uses software encoding with up to two encoder threads per camera, and the preview video is bounded by a four Mbps VBV maximum rate and a one Mbit buffer to avoid oversized keyframe bursts. The source resolution and the original recordings are not changed by any of this, so the transcoded preview is a viewing artefact and not a replacement of the recording.

The four settings, then, are a feature switch, a binary path, an idle timeout and a concurrency limit. That is a small surface, and each value maps to a cost you can see in the CPU and in the number of processes.

## The stack, the images and the ports

The Go module is named github.com/gowvp/owl and targets Go 1.27. The direct dependencies describe the shape of the service: Gin with the CORS and gzip middleware for the HTTP layer, GORM with MySQL, Postgres and SQLite drivers, golang-jwt for tokens, google/wire for dependency injection, an ONVIF client, the goddd framework the project is based on, an m3u8 library, a TOML parser, gopsutil for system metrics, gRPC and Protobuf, and the gosdp library for SDP handling.

The Dockerfile is deliberately plain. It starts from Alpine, sets the timezone to Asia/Shanghai, installs the CA certificates and the timezone data, copies the compiled binary for the target architecture and the www directory into the image, exposes port 15123 and runs the binary. So the frontend is baked into the image rather than served separately, and the whole application is a single process.

The compose file runs the published image with a restart policy, a hundred megabyte log cap, and the data directory mounted at the configuration path. The port list is where the protocol knowledge becomes concrete, and it is worth reading as a map of the standards in play. There is a port for the web admin and the ONVIF SOAP server, the ONVIF WS-Discovery port over UDP so that other devices on the LAN can find the platform, the GB28181 SIP signalling port over both TCP and UDP, the RTMP and RTSP ports for the media server, and a range of ports for receiving GB28181 media over both TCP and UDP.

That last point is the operational one. A camera has to be able to reach the platform's stream receiving ports, so the range has to be open from the camera network and not only from the admin network.

## Licensing, the separate frontend, and the documentation trail

Two things in the README deserve a careful reading before you build on this.

The first is licensing. For commercial licensing the README asks you to contact a WeChat account, and it states that unauthorized modifications must open-source both the frontend and the backend source code under the GPL license. The repository's own licence metadata is a custom classification, so the practical position is that the code is available, the terms are set out in the README, and anything commercial needs a conversation.

The second is that the web interface is not in this repository. The FAQ entry asks where the frontend resources are and how to load the web interface, and the answer is a link to a www.zip package in a separate releases page. You download the packaged frontend resources, place them in the project root, and name the directory www so it loads properly.

The FAQ also points at a documentation trail that is unusually thorough for a project of this kind. There is a series of open source diary articles on GB/T28181 covering the whole path from zero to implementing the protocol, setting up the server and solving CORS and API integration, building a monitoring dashboard with React components, using ESLint, completing forms with react-hook-form, integrating the jessibuca.js player in React, implementing RTMP authentication and playback, and a quick guide to GB28181 development. There are separate usage documents for RTMP push and pull stream rules, pushing to the platform with OBS, and doing the same from Hikvision and Dahua cameras.

The troubleshooting entry is also concrete: for a black screen when playing, check the ZLM settings button in the top right of Quick Desktop and the GB28181 stream receiving default address, and make sure that address is reachable by the surveillance device.

## Conclusion

Adopt Owl if you are building a private video platform for GB28181 cameras and need browser playback without plugins, because it implements the 2011, 2016 and 2022 versions of the standard and bridges the devices you already own. Do not adopt it expecting a turnkey product, because the web interface is a separate download that you place in the project root, the licence is a custom one that requires open sourcing both ends for unauthorised modifications, and the transcoding path needs FFmpeg with libx264 and libopus. Before you deploy it, read the owl_preview app rules, because a channel that already uses that name silently disables the automatic transcoding, and check the port list, because the SIP and stream receiving ports have to be reachable from the cameras themselves.

## FAQ

### What is GoWVP Owl and which standard does it implement?

An open-source GB28181 solution implemented in Go, a network video platform based on the GB28181-2022 standard that also supports the 2016 and 2011 versions, with ONVIF, RTMP and RTSP support. Its use cases include browser playback without plugins, mixed GB28181 and non-GB28181 devices, cross-network preview and deployment with Docker, Compose or Kubernetes.

### When does Owl start FFmpeg for preview transcoding?

Only for an H.265 source when the browser offers H.264 without H.265, after the channel's play token is validated and the browser's SDP and the source codec have been checked. The result is H.264 Baseline video and Opus audio, while H.264 sources and H.265-capable browsers use the original stream.

### What does the owl_preview app do in GoWVP?

It is the reserved app name for derived transcoded streams, with a per-job internal key. New channels cannot use it and existing ones cannot be moved into it, and if a conflict is detected at startup Owl logs a warning and disables automatic preview transcoding while preserving channel authentication, playback and recording. These streams are also excluded from recording and the AI callbacks.

### What are the configuration options for GoWVP preview transcoding?

Four entries in the Media section of configs/config.toml: PreviewDisabled, PreviewFFmpeg, PreviewIdleSeconds and PreviewMaxConcurrent. Omitted fields use the same defaults, an empty binary path resolves ffmpeg from the path, and zero idle and concurrency values mean thirty seconds and three cameras. FFmpeg must provide libx264 and libopus.

## Sources

- [gowvp/owl on GitHub](https://github.com/gowvp/owl)
- [Issues](https://github.com/gowvp/owl/issues)
- [Project website](http://gowvp.golang.space:15123)
- [README](https://github.com/gowvp/owl/blob/main/README.md)
- [Releases](https://github.com/gowvp/owl/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/gowvp-owl
