d 1718779a28 fix(audio): decode real samples from the audio stream
swr was configured for planar FLTP output while decodeAudio handed it a
single packed-buffer pointer, so the resampler never wrote into the Go
slice and every AudioFrame came back as zeros. Switch the output format
to packed FLT and pass the plane pointer through a C-allocated slot so
cgo accepts the handoff. Extend TestDecodeFrames to fail on all-zero
audio buffers.

Assisted-by: AGENT: Claude Sonnet 4.5
2026-09-26 11:22:40 +03:00
2026-06-07 10:01:58 +03:00
2026-06-07 10:00:34 +03:00
2026-06-07 00:40:27 +03:00
2026-06-07 00:40:27 +03:00
2026-06-07 10:00:34 +03:00
2026-06-07 00:40:27 +03:00
2026-06-07 00:40:27 +03:00
2026-06-07 00:40:27 +03:00
2026-06-07 00:40:27 +03:00
2026-06-07 00:40:27 +03:00
2026-06-07 00:40:27 +03:00
2026-06-06 18:04:56 +00:00
2026-06-07 00:44:14 +03:00

gopeg banner

gopeg

Go video decoding library with no runtime dependencies. FFmpeg is statically linked — import the package and get a single binary, no system installs required.

Installation

go get m8sh.su/x/gopeg

Requires CGO and a C compiler:

# arch/manjaro
sudo pacman -S gcc

# debian/ubuntu
sudo apt install gcc

Platforms

Platform Arch Status
linux/amd64 x86_64 ✅ Supported
linux/arm64 AArch64 ✅ Supported
windows/amd64 x86_64 ✅ Supported
darwin/amd64 x86_64 ✅ Supported
darwin/arm64 Apple Silicon ✅ Supported
web/wasm wasm32 🚧 Coming soon

Supported formats

Codecs
Video H.264, H.265/HEVC, AV1, VP9, VP8, MPEG-4
Audio AAC, MP3, Opus, Vorbis, FLAC, PCM
Containers MP4, MKV, WebM, AVI, FLV, OGG, IVF, MPEGTS

Features

  • Decode video frames as image.NRGBA — standard Go image type, no conversion needed
  • Decode audio frames as []float32 interleaved PCM
  • Read from any io.Reader — files, in-memory buffers, HTTP streams, pipes
  • Seeking supported when the reader implements io.Seeker; works without it too
  • Zero runtime dependencies — all FFmpeg and dav1d libs statically linked
  • Single go build for all supported platforms

Quick start

package main

import (
    "fmt"
    "image/png"
    "os"

    "m8sh.su/x/gopeg"
)

func main() {
    f, _ := os.Open("video.mp4")
    defer f.Close()

    dec, err := gopeg.NewDecoder(f)
    if err != nil {
        panic(err)
    }
    defer dec.Close()

    meta := dec.Meta()
    fmt.Printf("duration: %.2fs\n", meta.Duration.Seconds())
    fmt.Printf("video: %dx%d %s @ %d/%d fps\n",
        meta.Video.Width, meta.Video.Height,
        meta.Video.CodecName,
        meta.Video.FPSNum, meta.Video.FPSDen)
    if meta.Audio != nil {
        fmt.Printf("audio: %dHz %dch %s\n",
            meta.Audio.SampleRate,
            meta.Audio.Channels,
            meta.Audio.CodecName)
    }

    for {
        frame, err := dec.DecodeFrame()
        if err != nil {
            panic(err)
        }
        if frame == nil {
            break // EOF
        }
        if frame.Video != nil {
            f, _ := os.Create("frame.png")
            png.Encode(f, frame.Video.Img)
            f.Close()
        }
        if frame.Audio != nil {
            fmt.Printf("audio pts=%v samples=%d\n",
                frame.Audio.PTS,
                len(frame.Audio.Samples)/frame.Audio.Channels)
        }
    }
}

API

// Open a decoder from any reader. If r also implements io.Seeker,
// FFmpeg will use it for random access where the format requires it.
func NewDecoder(r io.Reader) (*Decoder, error)

// Returns duration, video/audio stream info. Safe to call before decoding.
func (d *Decoder) Meta() Meta

// Decode the next video or audio frame. Returns nil, nil on EOF.
// Frame.Video and Frame.Audio are mutually exclusive per call.
func (d *Decoder) DecodeFrame() (*Frame, error)

// Release all FFmpeg resources. Safe to call multiple times.
func (d *Decoder) Close()

Types

type Meta struct {
    Duration time.Duration
    Video    *VideoMeta  // nil if no video stream
    Audio    *AudioMeta  // nil if no audio stream
}

type VideoMeta struct {
    Width, Height int
    FPSNum, FPSDen int
    CodecName string
}

type AudioMeta struct {
    SampleRate int
    Channels   int
    CodecName  string
}

type Frame struct {
    Video *VideoFrame
    Audio *AudioFrame
}

type VideoFrame struct {
    Img      *image.NRGBA
    PTS      time.Duration
    Duration time.Duration
}

type AudioFrame struct {
    Samples    []float32  // interleaved
    Channels   int
    SampleRate int
    PTS        time.Duration
}

Building from source

The vendor/ directory contains prebuilt static libraries for all supported platforms. To rebuild from scratch, delete vendor/ and use the build script:

# arch/manjaro
sudo pacman -S gcc meson ninja nasm aarch64-linux-gnu-gcc mingw-w64-gcc

# debian/ubuntu
sudo apt install gcc meson ninja-build nasm gcc-aarch64-linux-gnu gcc-mingw-w64-x86-64
go run cmd/build/main.go

Script clones necessary repositories (default dav1d mirror and FFmpeg mirror), builds them into static libraries for linking with CGO with preset of required parameters and produces a vendor directory.

License

GPLv3 - this is free software. Contributions are welcome, feel free to open an issue.

S
Description
Go video decoding solution (no runtime dependencies)
Readme GPL-3.0
75 MiB
0 Stars 1 Watchers 0 Forks
2026-06-06 23:42:09 +00:00
Languages
Go 99%
C 1%