Open-source project
pion/mediadevices avatar
pion/mediadevices

pion/mediadevices: Go's MediaDevices API, Wired to cgo Codec Libraries

Go implementation of the MediaDevices API.

648 stars147 forksGoMIT

At a glance

What is it?
pion/mediadevices gives Go programs camera, microphone and screen capture plus a codec selector, at the cost of cgo codec libraries and explicit driver imports. It fits Go media pipelines that already live in the Pion/WebRTC world, not projects that want a pure-Go build.
Who is it for?
Adopt pion/mediadevices if your capture path already runs through Go and you accept cgo codec libraries: import the driver package for each input, wire a codec selector with NewCodecSelector, and build with -tags nomicrophone for cross-compilation. Do not adopt it for a pure-Go build or a browser-shaped MediaDevices replacement.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 8 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

The gap between Go and the browser's getUserMedia

A Go service that needs a camera frame has, historically, two bad options. It can shell out to ffmpeg and parse a pipe, or it can bind directly to V4L2 on Linux, AVFoundation on Mac and Media Foundation on Windows with three separate code paths. pion/mediadevices exists to collapse that into one call. The README describes it as a Go implementation of the MediaDevices API, and the usage example is deliberately shaped like the browser version: mediadevices.GetUserMedia takes a MediaStreamConstraints value, and you pull a track out of the returned stream with stream.GetVideoTracks()[0].

The audience is narrow and specific. This is for Go developers building media pipelines: WebRTC peers, RTP senders, HTTP MJPEG broadcasters, or a capture stage that feeds a machine learning model. The repository's examples directory lists exactly those shapes (webrtc, facedetection, rtp, http, archive). If you are writing a desktop GUI in another language, or you only need to read a file from disk, this library adds a cgo dependency for nothing. It solves capture and encode plumbing, not media processing.

Nothing is registered until you import a driver package

The library's most distinctive design decision is that it registers no media inputs by default. The README states this plainly: by default, there is no media input registered, and the reason given is to let you play only what you need. To get a camera you import the camera package for its side effects, with a blank identifier:

_ "github.com/pion/mediadevices/pkg/driver/camera"

That single line is what makes GetUserMedia able to find anything at all. Omit it and the constraints have no driver to satisfy them. The README also points at a dummy adapter, github.com/pion/mediadevices/pkg/driver/videotest, for machines without a camera or with unsupported adapters, which is the standard way to keep a capture pipeline testable in CI.

This is a real trade-off, not a free win. Compile-time registration means a binary carries only the inputs you asked for, but it also means the failure mode is silent until runtime. There is no import that errors because a driver is missing; the constraint simply does not resolve. The README's own comment in the example ("If you don't have a camera or your adapters are not supported, you can always swap your adapters with our dummy adapters below") is the only hint that this is where things go wrong.

The codec selector is where the cgo boundary sits

Encoding is handled by a separate layer. You construct codec parameters from a codec package, then hand them to mediadevices.NewCodecSelector, and pass that selector into GetUserMedia under the Codec field. The README's x264 example sets x264Params.Preset to x264.PresetMedium and x264Params.BitRate to 1_000_000, then builds the selector with mediadevices.WithVideoEncoders(&x264Params).

Here is the constraint that shapes deployment. The README says mediadevices does not implement the video or audio codecs itself; it calls system codec libraries through cgo. That means the codec library has to exist on the build machine and, depending on linking, on the target. The README gives installation commands per codec: brew install x264 on Mac, apt install libx264-dev on Ubuntu. The mmal package, for H264 hardware encoding on Raspberry Pi or boards with VideoCore GPUs, is described as needing no installation because mmal comes built in on Raspberry Pi.

The library also declines to pick a codec for you. The README states that it does not provide recommendations on choosing one codec or another, calling the choice complex and subjective. That is an honest position, and it pushes the decision onto you along with the platform dependency it implies.

Reading frames without copying them

The frame path is worth understanding before you build on it. You cast the track to *mediadevices.VideoTrack, call NewReader(false) to get a video reader, and then call Read(), which returns three values: the frame, a release function, and an error. The release function is not optional bookkeeping. The README explains that release returns the buffer holding the frame back to the source so it can be reused for the next frames.

That is a buffer pool, and it means the frame is only valid until you release it. If you hold a frame reference past the release call, you are reading memory the capture source is free to overwrite. The API makes this explicit rather than hiding it, which is the right call, but it also means every consumer of frames has to respect the lifetime. In the README example the frame is encoded to JPEG before the deferred release runs, which is the safe ordering.

What you get back is a standard image.Image. The README notes this makes frames compatible with the Go standard library, which is why the example can call jpeg.Encode directly on the result. That compatibility is the payoff for the whole capture stack: once you have the frame, nothing downstream needs to know mediadevices exists.

Cross-compiling: the nomicrophone build tag

Audio is where the build story gets awkward. The README documents a nomicrophone build tag, applied as go build -tags nomicrophone, and gives two reasons to use it: cross-compilation where CGO dependencies like malgo are unavailable, and projects where audio is not required.

Read that carefully. The tag exists because the microphone path pulls in a CGO dependency that cannot be satisfied in a cross-compilation environment. So a Go program that wants video capture on a build server and ships to a different architecture has to decide between dropping microphone support entirely or setting up a cross-compilation toolchain that can build the audio dependency. There is no documented middle ground in the supplied material.

If your application is video-only, the tag is free and you should set it. If it needs audio and you cross-compile, this is the point where you should test the build before committing to the dependency, because it is the constraint most likely to force a change in how you ship.

Platform coverage and what the README does not say

The input table in the README lists Camera, Microphone and Screen across Linux, Mac and Windows, all three marked as supported in each column. That is the extent of the platform claims in the supplied material. There is no per-platform caveat, no note about which screen capture backend is used on which OS, and no statement about capture resolution or framerate limits.

Treat the table as a statement of intent rather than a specification. The repository layout does show a pkg/driver tree with per-input packages, and the README's build-tag section confirms that at least the microphone path carries a platform-sensitive CGO dependency. Beyond that, the material does not tell you what the screen capture driver requires on each OS, so that is the first thing to check in the driver package you intend to import.

The README also does not document error handling. Every call in the example discards its error with a blank identifier, including GetUserMedia and the reader's Read. That is normal for a README snippet, but it means the failure taxonomy (no driver registered, no codec available, device busy) is not described anywhere in the supplied text. You will be reading the package source to find out.

Where pion/mediadevices is the wrong tool

The clearest case against it is a build that must stay pure Go. The codec layer is cgo by design, and the README says so directly: mediadevices needs to call the codec libraries from the system through cgo, and you are required to install the codec libraries before you can use them. If you want a single static binary with no system library dependencies, this library cannot give you one for the encoding path. Capture alone may be lighter, but the README does not break down which drivers are pure Go and which are not.

The second case is a project that only needs one platform and one input. If you are capturing a camera on Linux only and never encoding, a direct V4L2 binding is a smaller dependency with no codec selector to configure and no blank-import registration to remember. mediadevices earns its place when you need the cross-platform abstraction or the encode step, not when you need one ioctl wrapper.

The third case is anything expecting the browser MediaDevices API surface. This is described as a Go implementation of that API, not a port of it. The constraint types are Go types (prop.Int for width and height), the track model is Pion's, and there is no enumerateDevices equivalent shown in the supplied material. If your mental model comes from JavaScript, expect to relearn the shape.

Maintenance, versioning and the MIT licence

The repository is not archived and the last push recorded is 2026-09-07. Recent releases run v0.9.3 and v0.9.4 in February 2026, then v0.10.0 in April 2026. The project is still on a v0.x version line, which in Go module terms means the maintainers have not committed to API stability. A v0.9 to v0.10 bump is allowed to break things, and the module path carries no /v2 suffix that would signal a stability promise.

Upgrade cost therefore depends on how much of the API you touch. The surface you are most likely to bind to is small (GetUserMedia, MediaStreamConstraints, MediaTrackConstraints, NewCodecSelector, the track and reader types), but the codec parameter structs are per-codec and can change independently. Pinning a version in go.mod and reading the release notes before bumping is the practical approach with a v0.x dependency.

The licence is MIT, per the README badge and the LICENSE file it links. MIT is permissive: it allows use, modification and redistribution provided the copyright notice and permission notice are retained. That is a statement about the licence text, not legal advice for your situation. Note that the cgo codec libraries you link against have their own licences, and x264 in particular is a separate project with its own terms. The MIT licence on mediadevices does not extend to them.

Editorial conclusion

Adopt pion/mediadevices if your capture path already runs through Go and you accept cgo codec libraries: import the driver package for each input, wire a codec selector with NewCodecSelector, and build with -tags nomicrophone for cross-compilation. Do not adopt it for a pure-Go build or a browser-shaped MediaDevices replacement. Verify first that your target OS has the codec library available (for example libx264-dev on Ubuntu or brew install x264 on Mac) and that your driver import set matches the inputs you actually ship.

Official sources

  1. License: MIT
  2. pion/mediadevices on GitHub
  3. Project website
  4. README
  5. Releases
Community notes

Community notes