Documentation
¶
Overview ¶
Package oggopus implements Ogg page framing and the Ogg Opus mapping.
Page, PacketReader, and PacketWriter provide low-level Ogg framing with CRC, lacing, continuation, serial-number, and sequence validation. Reader and Writer add OpusHead, OpusTags, packet-duration, pre-skip, end-trim, and 48 kHz granule-position handling. Reader advances through chained logical streams automatically and can seek with RFC 7845 decoder pre-roll when its source implements io.ReadSeeker. Writer creates one logical stream; callers create a chain by finishing each Writer and creating another on the same destination. Multiplexed physical streams are not demultiplexed.
Reader, Writer, PacketReader, and PacketWriter are stateful, must not be copied after first use, and are not safe for concurrent use. They borrow their io.Reader or io.Writer for their lifetime but do not close it. Parsed packet and metadata byte slices are caller-owned copies. This package validates and transports Opus packets; it does not decode PCM. Public operations may wrap the sentinel errors below; use errors.Is to test them.
Index ¶
Examples ¶
Constants ¶
const ( // OpusHeadSignature identifies an Ogg Opus identification header. OpusHeadSignature = "OpusHead" // OpusTagsSignature identifies an Ogg Opus comment header. OpusTagsSignature = "OpusTags" )
const ( // CapturePattern is the four-byte signature at the start of every Ogg page. CapturePattern = "OggS" // StreamVersion is the supported Ogg page format version. StreamVersion = 0 // MaxSegments is the maximum number of lacing values on one Ogg page. MaxSegments = 255 // MaxPageData is the maximum payload size represented by one lacing table. MaxPageData = 255 * 255 )
Variables ¶
var ( // ErrInvalidCapture indicates that an Ogg page does not start with "OggS". ErrInvalidCapture = errors.New("oggopus: invalid Ogg capture pattern") // ErrUnsupportedVersion indicates an unsupported Ogg page version. // Unsupported OpusHead versions are reported as ErrInvalidOpusHead. ErrUnsupportedVersion = errors.New("oggopus: unsupported version") // ErrInvalidHeaderType indicates that an Ogg page uses reserved flags. ErrInvalidHeaderType = errors.New("oggopus: invalid page header type") // ErrInvalidPage indicates inconsistent Ogg page fields or lacing. ErrInvalidPage = errors.New("oggopus: invalid Ogg page") // ErrChecksum indicates that an encoded Ogg page failed CRC verification. ErrChecksum = errors.New("oggopus: page checksum mismatch") // ErrSerial indicates an unexpected or reused logical-stream serial number. ErrSerial = errors.New("oggopus: unexpected bitstream serial number") // ErrSequence indicates a non-consecutive Ogg page sequence number. ErrSequence = errors.New("oggopus: non-consecutive page sequence number") // ErrUnexpectedContinue indicates a continued page without a packet prefix. ErrUnexpectedContinue = errors.New("oggopus: unexpected continued packet") // ErrMissingContinue indicates a partial packet followed by a fresh page. ErrMissingContinue = errors.New("oggopus: missing continued packet page") // ErrTruncatedPacket indicates an unfinished packet at logical stream end. ErrTruncatedPacket = errors.New("oggopus: truncated packet") // ErrAfterEOS indicates page data encountered after an EOS page. // // Deprecated: PacketReader.Next returns io.EOF after draining its EOS page, // so normal public iteration does not expose data after EOS. The sentinel is // retained for v1 compatibility. ErrAfterEOS = errors.New("oggopus: data after end-of-stream page") // ErrInvalidOpusHead indicates a malformed or inconsistent OpusHead packet. ErrInvalidOpusHead = errors.New("oggopus: invalid OpusHead packet") // ErrInvalidOpusTags indicates a malformed or non-UTF-8 OpusTags packet. ErrInvalidOpusTags = errors.New("oggopus: invalid OpusTags packet") // ErrInvalidOpusStream indicates invalid Ogg Opus headers, timing, or layout. ErrInvalidOpusStream = errors.New("oggopus: invalid Ogg Opus stream") // ErrWriterClosed indicates an operation on a finalized writer. ErrWriterClosed = errors.New("oggopus: writer is closed") // ErrInvalidGranule indicates an invalid packet granule position or an // attempt to close a PacketWriter before any complete packet was written. ErrInvalidGranule = errors.New("oggopus: invalid granule position") // ErrNotSeekable indicates that Reader.SeekPCM has no io.ReadSeeker source. ErrNotSeekable = errors.New("oggopus: source is not seekable") // ErrSeekOutOfRange indicates a sample outside the current logical stream. ErrSeekOutOfRange = errors.New("oggopus: seek sample is out of range") )
Functions ¶
Types ¶
type Head ¶
type Head struct {
// Version is the OpusHead version. Values 1 through 15 are accepted.
Version uint8
// Channels is the decoded output channel count.
Channels uint8
// PreSkip is the number of decoded samples per channel to discard at 48 kHz.
PreSkip uint16
// InputSampleRate is an informational source-rate hint in Hz.
InputSampleRate uint32
// OutputGain is the playback gain in signed Q7.8 dB.
OutputGain int16
// MappingFamily selects the channel mapping semantics.
MappingFamily uint8
// StreamCount is the number of elementary Opus streams for nonzero families.
StreamCount uint8
// CoupledCount is the number of stereo elementary streams.
CoupledCount uint8
// ChannelMapping maps output channels to coded channels. A value of 255
// means silence. Family 0 requires this slice to be empty.
ChannelMapping []uint8
}
Head is the Ogg Opus identification header. PreSkip and granule positions are measured at 48 kHz. OutputGain is signed Q7.8 dB.
func ParseHead ¶
ParseHead validates and parses an OpusHead packet. ChannelMapping is copied. Version 1 requires the exact defined length; trailing fields accepted for versions 2 through 15 are not preserved.
func (Head) MarshalBinary ¶
MarshalBinary validates h and returns a new encoded OpusHead packet. Fields unknown to this implementation cannot be represented for future versions.
type HeaderType ¶
type HeaderType byte
HeaderType contains the Ogg page header flags.
const ( // HeaderContinued marks a page beginning with a continued packet. HeaderContinued HeaderType = 0x01 // HeaderBOS marks the first page of a logical bitstream. HeaderBOS HeaderType = 0x02 // HeaderEOS marks the final page of a logical bitstream. HeaderEOS HeaderType = 0x04 )
type Packet ¶
type Packet struct {
// Data is a caller-owned copy of the reconstructed packet payload.
Data []byte
// GranulePosition is meaningful only for the last packet completed on a
// page; it is -1 for earlier packets. Reader interprets it in 48 kHz samples.
GranulePosition int64
// Duration48k is the decoded packet duration per channel at 48 kHz.
// PacketReader leaves it zero; Reader populates it for audio packets.
Duration48k int
// DiscardStart is the decoded samples per channel to remove at the beginning
// for Opus pre-skip or seeking. Reader populates it.
DiscardStart int
// DiscardEnd is the decoded samples per channel to remove at the end for
// granule-position trimming. Reader populates it.
DiscardEnd int
// LinkIndex is the zero-based chained logical-stream index. PacketReader
// leaves it zero; Reader populates it for audio packets.
LinkIndex int
// Serial identifies the packet's logical bitstream.
Serial uint32
// PageSequence is the sequence number of the page completing the packet.
PageSequence uint32
// BOS reports whether the packet begins on a BOS page.
BOS bool
// EOS reports whether the packet is the last packet completed on an EOS
// page. An empty EOS page produces no Packet value.
EOS bool
// FirstPacketOnPage reports whether this is the first packet completed on
// its page; a continued packet may have begun on an earlier page.
FirstPacketOnPage bool
// LastPacketOnPage reports whether this is the final packet completed on its page.
LastPacketOnPage bool
}
Packet is a reconstructed Ogg packet. GranulePosition is meaningful only for the last packet completed on a page; it is -1 for earlier packets.
type PacketReader ¶
type PacketReader struct {
// contains filtered or unexported fields
}
PacketReader reconstructs packets from a single logical Ogg bitstream.
func NewPacketReader ¶
func NewPacketReader(r io.Reader) *PacketReader
NewPacketReader returns a stateful packet reader that borrows r. It accepts one logical bitstream and enforces its serial number and page sequence.
func (*PacketReader) EOS ¶
func (r *PacketReader) EOS() bool
EOS reports whether an EOS page has been read. Queued packets from that page may still remain to be returned by Next.
func (*PacketReader) Next ¶
func (r *PacketReader) Next() (Packet, error)
Next returns the next complete packet with caller-owned Data. It validates page CRCs, serial numbers, sequences, and continuation. After queued EOS packets are returned, it returns io.EOF.
func (*PacketReader) Serial ¶
func (r *PacketReader) Serial() (uint32, bool)
Serial returns the logical-stream serial and whether a page has established it.
type PacketWriteOptions ¶
type PacketWriteOptions struct {
// GranulePosition becomes the page granule when this packet is the last
// packet completed on that page. Only non-negativity is validated.
GranulePosition int64
// Flush finishes the current page after adding the packet.
Flush bool
// EOS finishes an EOS page and closes the PacketWriter. It takes precedence
// over Flush.
EOS bool
}
PacketWriteOptions controls page metadata for a packet.
type PacketWriter ¶
type PacketWriter struct {
// contains filtered or unexported fields
}
PacketWriter writes packets into a single logical Ogg bitstream. It packs packets into pages until Flush is requested or the 255-segment page limit is reached. The first page is marked BOS automatically. It borrows its output writer, copies packet bytes during WritePacket, and is not safe for concurrent use.
func NewPacketWriter ¶
func NewPacketWriter(w io.Writer, serial uint32) *PacketWriter
NewPacketWriter returns a packet writer with page sequence zero for serial.
func (*PacketWriter) Close ¶
func (w *PacketWriter) Close() error
Close writes an EOS page. If the final packet was already flushed, Close emits an empty EOS page carrying the final granule position. A successful Close is idempotent and does not close the underlying writer.
func (*PacketWriter) Flush ¶
func (w *PacketWriter) Flush() error
Flush finishes the current page without ending the logical stream. It is a no-op when no segments are buffered and does not flush or close the underlying writer.
func (*PacketWriter) Sequence ¶
func (w *PacketWriter) Sequence() uint32
Sequence returns the sequence number of the next page to be written.
func (*PacketWriter) Serial ¶
func (w *PacketWriter) Serial() uint32
Serial returns the logical-stream serial number.
func (*PacketWriter) WritePacket ¶
func (w *PacketWriter) WritePacket(data []byte, options PacketWriteOptions) error
WritePacket adds one packet. For Ogg Opus, callers normally supply the total decoded 48 kHz samples per channel through the packet. This low-level writer accepts zero-length packets and checks neither Opus framing nor timing; it only requires a non-negative granule position.
type Page ¶
type Page struct {
// Version is the Ogg page format version and must equal StreamVersion.
Version byte
// HeaderType contains the continued, BOS, and EOS flags.
HeaderType HeaderType
// GranulePosition is the codec-defined position for the page, or -1 when no
// completed packet on the page establishes one. Ogg Opus uses 48 kHz samples.
GranulePosition int64
// Serial identifies the logical bitstream.
Serial uint32
// Sequence is the zero-based page sequence number within the bitstream.
Sequence uint32
// Checksum is populated by ParsePage. MarshalBinary ignores this value and
// computes the checksum from the other fields.
Checksum uint32
// Segments contains the lacing values whose sum must equal len(Data).
Segments []byte
// Data contains the concatenated segment payload.
Data []byte
}
Page is one complete Ogg page. Segments contains its lacing values and Data contains the concatenated segment payload.
func ParsePage ¶
ParsePage parses exactly the first page in data and returns the number of bytes consumed. It verifies the capture pattern, version, flags, length, lacing table, and CRC. Trailing data is left unconsumed, and returned lacing and payload slices are caller-owned copies. Truncation returns io.ErrUnexpectedEOF.
func ReadPage ¶
ReadPage reads and CRC-verifies exactly one page from r. It returns io.EOF when no page bytes are available and io.ErrUnexpectedEOF for a partial page.
func (Page) MarshalBinary ¶
MarshalBinary encodes a page and calculates its Ogg CRC checksum.
type Reader ¶
type Reader struct {
// Head is the current logical stream's identification header. It is updated
// when NextPacket advances to a chained stream.
Head Head
// Tags is the current logical stream's comment header. It is updated when
// NextPacket advances to a chained stream.
Tags Tags
// contains filtered or unexported fields
}
Reader parses an Ogg Opus physical stream, including chained logical streams. It borrows its source, is stateful, and is not safe for concurrent use. It does not demultiplex interleaved logical streams.
func NewReader ¶
NewReader synchronously reads and validates the first logical stream's mandatory OpusHead and OpusTags packets. SeekPCM is available only when r also implements io.ReadSeeker.
func (*Reader) EOS ¶
EOS reports whether an EOS page for the current link has been read. Pending packets or another chained logical stream may still remain.
func (*Reader) Link ¶ added in v1.3.0
Link returns the zero-based index of the current chained logical stream.
func (*Reader) NextPacket ¶
NextPacket returns the next duration-validated Opus audio packet and its Ogg timing metadata. DiscardStart and DiscardEnd tell a decoder how many samples per channel to omit. At a logical-stream boundary it advances automatically and updates Head, Tags, Serial, and Link; io.EOF means the physical stream is exhausted. A terminal format error is returned again on later calls.
func (*Reader) SeekPCM ¶ added in v1.3.0
SeekPCM positions the reader for a playable sample in the current logical stream, measured per channel at 48 kHz after pre-skip. The valid range is zero through the link's playable length, inclusive. It performs the RFC 7845 80 ms decoder pre-roll: NextPacket may return earlier packets first and marks all samples before the target in DiscardStart.
SeekPCM returns ErrNotSeekable unless the source passed to NewReader implements io.ReadSeeker, and ErrSeekOutOfRange for an invalid sample. A target at the playable length positions the reader at the link boundary, so the next call may advance to a chained stream. Seeking mutates reader and source state and must not run concurrently with other Reader operations.
type Tags ¶
type Tags struct {
// Vendor identifies the producing application.
Vendor string
// Comments contains UTF-8 comment entries, conventionally NAME=value.
Comments []string
// Extra is optional trailing binary data preserved during parsing and marshaling.
Extra []byte
}
Tags is an Ogg Opus comment header. Vendor and Comments must be UTF-8. Comment field-name syntax is not otherwise enforced. Extra preserves optional trailing binary data.
func ParseTags ¶
ParseTags validates and parses an OpusTags packet. Extra and the returned strings do not alias packet.
func (Tags) MarshalBinary ¶
MarshalBinary validates t and returns a new encoded OpusTags packet.
type Writer ¶
type Writer struct {
// contains filtered or unexported fields
}
Writer writes one complete Ogg Opus logical bitstream. It is stateful, is not safe for concurrent use, and borrows but does not close its destination. To create a chained stream, finish one Writer and create another on the same destination with a different serial number.
Example ¶
package main
import (
"bytes"
"fmt"
"github.com/darui3018823/opus"
"github.com/darui3018823/opus/oggopus"
)
func main() {
encoder, err := opus.NewEncoder(opus.SampleRate48kHz, 1, opus.ApplicationAudio)
if err != nil {
panic(err)
}
encoded, err := encoder.Encode(make([]int16, opus.FrameSize20ms), opus.FrameSize20ms)
if err != nil {
panic(err)
}
var container bytes.Buffer
writer, err := oggopus.NewWriter(
&container,
1,
oggopus.Head{
Version: 1,
Channels: 1,
PreSkip: uint16(encoder.Lookahead()),
InputSampleRate: opus.SampleRate48kHz,
},
oggopus.Tags{Vendor: "example"},
)
if err != nil {
panic(err)
}
if err := writer.WritePacket(encoded, oggopus.PacketWriteOptions{
GranulePosition: opus.FrameSize20ms,
}); err != nil {
panic(err)
}
if err := writer.Close(); err != nil {
panic(err)
}
reader, err := oggopus.NewReader(bytes.NewReader(container.Bytes()))
if err != nil {
panic(err)
}
packet, err := reader.NextPacket()
if err != nil {
panic(err)
}
fmt.Printf(
"vendor=%q duration=%d eos=%t same-packet=%t\n",
reader.Tags.Vendor,
packet.Duration48k,
packet.EOS,
bytes.Equal(packet.Data, encoded),
)
}
Output: vendor="example" duration=960 eos=true same-packet=true
func NewWriter ¶
NewWriter validates and writes the mandatory headers. The ID header is placed alone on page 0, and the comment header finishes page 1.
func (*Writer) Close ¶
Close writes an EOS page when needed. It is idempotent after success and does not close the underlying writer.
func (*Writer) WritePacket ¶
func (w *Writer) WritePacket(data []byte, options PacketWriteOptions) error
WritePacket writes one non-empty packet. Set EOS on the final packet to end the stream on the same page; otherwise Close emits an empty EOS page. This method does not validate Opus framing, duration, or granule timing.