oggopus

package
v0.1.29 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 31, 2026 License: MIT Imports: 8 Imported by: 0

Documentation

Overview

Package oggopus muxes Opus packets into an Ogg-Opus stream.

It is a pure-Go muxer and never decodes, re-encodes or inspects audio: packets handed to Writer.WritePacket are written through byte-identically, framed by the OpusHead and OpusTags pages the Opus-in-Ogg mapping requires. That is what makes it usable in a CGO_ENABLED=0 build with no libopus present.

Granule positions are always 48 kHz

Ogg granule positions for Opus are defined in 48 kHz units regardless of the stream's real capture rate, so an 8 kHz recording advances its granule exactly as a 48 kHz one does for the same wall-clock duration. The capture rate is recorded in OpusHead for players to report, and is not used for timing.

WritePacket takes milliseconds, not sample counts

The duration of a packet is not derivable from its bytes without decoding it, and the codec header's frame-size field cannot be trusted: on real networks it disagrees with the stream's actual packet_duration. WritePacket therefore takes the duration the protocol reported for that packet, in milliseconds, and scales it to granules itself. Feed it the protocol's packet_duration, never a value derived from the codec header.

Recovering a file whose writer died

Probe reads a file's duration back without decoding it, from the granule position of the last intact page. It does not need an end-of-stream page, so it works on a recording that a crash interrupted — which is the case an unattended recorder actually has to survive. Writer.Flush bounds how much audio such a crash can lose, and how much of it Probe can see.

A disk that fills is handled the same way round: the first failed page write stops the Writer permanently rather than carrying on and leaving a gap in the page sequence, so the file ends cleanly at its last complete page and Probe still reads it. See Writer for the details.

Index

Constants

This section is empty.

Variables

View Source
var ErrNoOggPage = errors.New("oggopus: no Ogg page found")

ErrNoOggPage reports a file with no recognisable Ogg page in its tail.

Functions

func Probe

func Probe(path string) (time.Duration, error)

Probe returns the audio duration of an Ogg-Opus file by reading the granule position of the last complete page in its tail. It works on files that were never closed (no EOS page), which is how a recording interrupted by a crash is recovered without decoding audio.

A candidate page is believed only once its CRC checks out, because the "OggS" capture pattern also occurs by chance inside audio data. One consequence is deliberate: in a file truncated mid-page the final partial page fails its CRC and is skipped, so Probe reports the last *intact* page's granule. A truncated page's granule claims packets that are not fully present, so this makes Probe a lower bound on the real duration rather than an occasionally optimistic guess.

func ReadPackets

func ReadPackets(r io.Reader) ([][]byte, error)

ReadPackets walks an Ogg-Opus stream and returns the audio packets in order — everything after the OpusHead and OpusTags packets — tolerating a truncated final page (a crash mid-write), which is dropped. It is what the archive's Replay reads a file back into frames with.

Types

type Writer

type Writer struct {
	// contains filtered or unexported fields
}

Writer muxes Opus packets into an Ogg-Opus stream.

Packets are written through unmodified; the Writer never decodes or re-encodes audio. Not safe for concurrent use.

A write failure is permanent

Once a page write fails the Writer is failed for good: WritePacket, Flush and Close all return that first error and emit nothing further. Resuming would be worse than stopping. A decoder reads a jump in page sequence numbers as a hole in the stream, and a Write that failed part-way through a page has already put bytes on disk that re-emitting the same packets would duplicate. So a recorder whose disk fills leaves a file that simply ends at its last complete page, which Probe can still read, instead of one that is subtly corrupt.

func NewWriter

func NewWriter(w io.Writer, sampleRate uint32, channels uint8) (*Writer, error)

NewWriter starts an Ogg-Opus stream, writing the OpusHead and OpusTags pages immediately. sampleRate is the original capture rate recorded in OpusHead; channels must be 1 or 2 (channel mapping family 0).

func (*Writer) Close

func (ow *Writer) Close() error

Close flushes buffered packets, marks end of stream and is idempotent. It does not close the underlying writer.

On a failed Writer it writes no end-of-stream page and returns the latched error instead of nil, every time it is called.

func (*Writer) Duration

func (ow *Writer) Duration() time.Duration

Duration reports the audio duration accumulated so far.

granule only ever advances by whole milliseconds (durationMs * granulesPerMs), so dividing first is exact. Doing it the other way round — `time.Duration(granule) * time.Second / 48000` — overflows int64 once granule passes ~9.2e9, i.e. after about 53 hours of accumulated audio, and silently returns a wrapped value.

func (*Writer) Flush

func (ow *Writer) Flush() error

Flush writes any buffered packets as a page. It bounds how much audio a crash can lose, and makes everything written so far visible to Probe. Close flushes implicitly.

Up to maxPageBody of audio can otherwise sit in memory — seconds of it at 8 kHz — so an unattended recorder that never calls this loses that much on every crash, and Probe cannot see it either.

func (*Writer) WritePacket

func (ow *Writer) WritePacket(opus []byte, durationMs uint32) error

WritePacket appends one Opus packet. durationMs is the packet's duration in milliseconds, taken from the protocol's packet_duration field; the granule position advances by durationMs * 48.

Source Files

  • crc.go
  • doc.go
  • page.go
  • probe.go
  • reader.go
  • writer.go

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL