container

package
v0.0.0-...-4820260 Latest Latest
Warning

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

Go to latest
Published: Sep 1, 2026 License: BSD-3-Clause Imports: 22 Imported by: 0

Documentation

Overview

Package container reads and writes time-based media containers in pure Go (CGO=0).

Demux reads MP4/ISO-BMFF, Matroska/WebM and MPEG-TS, exposing each elementary stream's metadata (kind, codec, dimensions, timing). Reader goes further on all three, handing back a track's samples and the configuration needed to write it elsewhere — which is what turns an HLS segment, or a WebM, into an MP4. Muxer writes a fragmented MP4 from tracks that arrive separately — which is what a DASH presentation, keeping its video and audio apart, requires — and never re-encodes: samples are written as handed over. ProgressiveMuxer writes those same tracks as an ordinary MP4 instead — one mdat and the sample tables that address it — which is what a player with no streaming stack, a hardware decoder, or a tool that seeks by sample table expects. TSMuxer writes them as an MPEG-TS, which is what an HLS segment is, so remuxing runs in both directions, and WebMMuxer writes them as a Matroska/WebM file — the container a browser plays with no plugin — leaving the segment's size unstated when the caller cannot seek.

Reader and the muxers are counterparts: what one hands back, the others take, so a track can be copied from one file into another without the caller reading a single box.

Remux, Cut and Concat stand on those two and offer the whole-file operations a caller would otherwise reach for ffmpeg to perform — copy a file, keep the stretch between two time offsets, join files that match, leave a track out — none of which touches a sample's bytes.

Codec bitstream decoding lives in sibling packages.

Index

Constants

View Source
const DefaultBrand = "iso5"

DefaultBrand is the major brand written in ftyp.

View Source
const DefaultChunkDuration = 500 * time.Millisecond

DefaultChunkDuration is how much of one track's media a chunk holds when the caller states nothing: short enough that the tracks stay interleaved for a player reading the file front to back, long enough that the chunk tables stay small.

View Source
const DefaultClusterDuration = time.Second

DefaultClusterDuration is how much media a cluster holds when the caller states nothing. A second is what the browsers' own muxers settle on: short enough that a player scanning for a start point does not read far, long enough that the per-cluster overhead stays negligible.

View Source
const DefaultFragmentDuration = 2 * time.Second

DefaultFragmentDuration is how much media a fragment holds when the caller states nothing: long enough to keep the overhead low, short enough that a player can start on the first one.

View Source
const DefaultMediaMemoryLimit = 256 << 20

DefaultMediaMemoryLimit is how much media data a progressive muxer holds before it gives up. See ProgressiveMuxer for what is held and why.

View Source
const DefaultProgressiveBrand = "isom"

DefaultProgressiveBrand is the major brand written in the ftyp of a progressive file. It is not the brand a fragmented file claims: a progressive file promises none of the segment structure a DASH brand announces.

View Source
const DefaultTimestampScale = time.Millisecond

DefaultTimestampScale is how long one segment tick lasts when the caller states nothing: a millisecond, the Matroska default, and the only scale the WebM guidelines expect a browser to meet.

View Source
const TSTimescale = 90000

TSTimescale is the clock every timestamp in a transport stream is counted in.

Variables

View Source
var (
	// ErrNoConfiguration means the samples carry nothing that describes the
	// track: no parameter set, no sequence header, no key frame.
	ErrNoConfiguration = errors.New("container: the samples describe no configuration")
	// ErrCodecMismatch means the samples do not hold the codec the caller
	// stated. The codec is never inferred from the bitstream — the two
	// NAL-based codecs spell a unit's type in different bits of its header,
	// and reading one as the other turns a picture into a parameter set — so
	// a contradiction is reported instead of resolved.
	ErrCodecMismatch = errors.New("container: the samples do not hold the stated codec")
)

Errors reported when deriving a configuration from samples.

View Source
var (
	// ErrNoTracks means Close or a write was reached before any track was
	// declared.
	ErrNoTracks = errors.New("container: no track to write")
	// ErrUnknownTrack means the sample names a track this muxer does not
	// have.
	ErrUnknownTrack = errors.New("container: unknown track")
	// ErrTrackConfig means a track configuration is incomplete or does not
	// describe what its codec needs.
	ErrTrackConfig = errors.New("container: invalid track configuration")
	// ErrUnsupportedCodec means the codec cannot be described in a sample
	// entry by this muxer yet.
	ErrUnsupportedCodec = errors.New("container: unsupported codec")
	// ErrClosed means the muxer was used after Close.
	ErrClosed = errors.New("container: muxer is closed")
	// ErrSample means the sample cannot be written as given.
	ErrSample = errors.New("container: invalid sample")
)

Errors reported when writing a container.

View Source
var (
	// ErrNoSamples means the container holds no readable sample for the
	// track, either because its tables are absent or because it is empty.
	ErrNoSamples = errors.New("container: no readable sample")
	// ErrSampleData means a sample table points outside the file.
	ErrSampleData = errors.New("container: sample data out of range")
	// ErrUnsupportedFormat means samples cannot be read from this format
	// yet.
	ErrUnsupportedFormat = errors.New("container: reading samples from this format is not supported")
)

Errors reported when reading samples.

View Source
var (
	// ErrTrackMismatch means two inputs cannot be joined as they are,
	// because a decoder would have to be reconfigured between them.
	ErrTrackMismatch = errors.New("container: tracks do not match")
	// ErrTimeRange means the range asked for cannot be cut.
	ErrTimeRange = errors.New("container: invalid time range")
	// ErrNoSyncSample means a track offers no sample a decoder may start at,
	// so a cut cannot begin there.
	ErrNoSyncSample = errors.New("container: no sync sample to cut at")
)

Errors reported by the remux helpers.

View Source
var ErrMatroska = errors.New("container: matroska")

ErrMatroska means the Matroska document states something that cannot be carried over as samples: a timestamp scale no timescale can express, a laced block nothing times, a last sample nothing measures.

View Source
var ErrMediaTooLarge = errors.New("container: media data exceeds the muxer's memory limit")

ErrMediaTooLarge means the media a progressive file has to hold before its sample tables can be written exceeds what the muxer is allowed to keep in memory. A writer that can seek reports it only for a single sample larger than the whole limit, having handed everything else over already.

View Source
var ErrNoProgram = errors.New("container: transport stream declares no program")

ErrNoProgram means the stream never described what it carries.

Functions

func Concat

func Concat(w io.Writer, srcs []*Reader, opts ...RemuxOption) error

Concat writes srcs one after another into a single fragmented MP4, tracks paired by position in Reader.TrackIDs order — the first input's tracks are the ones the output declares.

Only inputs whose paired tracks would not force a decoder to be reconfigured can be joined this way; anything else is refused with an error naming the track and what differs about it. Language is not part of that comparison: it says nothing about how a sample decodes, and the first input's wins.

Timestamps need no shifting: each track's clock continues from the durations already written, so the second input follows the first instead of overlapping it. The consequence is that a track drifts against its siblings by whatever their durations differ by inside each input — go-avkit inserts no silence and duplicates no frame to hide it.

func Cut

func Cut(w io.Writer, src *Reader, start, end time.Duration, opts ...RemuxOption) error

Cut copies the samples of src that fall between start and end, as Remux otherwise would. An end of zero or less means "to the end of the input".

A cut can only begin at a sync sample: a decoder handed anything else cannot reconstruct the first pictures. So start is snapped *back* to the last sync sample at or before it, which means the output may begin earlier than asked — by up to one group of pictures. RequireSyncStart turns that snap into an error instead. When the cut falls before the first sync sample of all, the output begins at that one, later than asked.

The sync grid of the first video track decides where the cut lands; every other track then starts at its own last sync sample at or before that point. Since each track's output timeline restarts at zero, a track whose sync grid is coarser than the video's is advanced against it by that difference — up to one audio frame in practice. Sample-accurate alignment would need an edit list, which the muxer does not write.

Samples are kept whole: end excludes the first sample that starts at or after it rather than truncating one, and a range that selects nothing still writes a file naming its tracks.

func HoldsFragments

func HoldsFragments(src io.ReaderAt) (bool, error)

HoldsFragments reports whether an MP4 states its samples in fragments rather than in the sample tables of its moov.

Only the box headers are read: walking them by the size each states costs one read apiece, where decoding the file to ask would cost the film. A file that is not an MP4 at all simply holds no fragments.

func Join

func Join(w io.Writer, srcs []*Reader, opts ...RemuxOption) error

Join writes the tracks of every input side by side into one file: the picture of one and the sound of another become one film. Where Concat puts its inputs one after another in time, Join puts them together in it — which is what a stream delivered as separate representations needs, since a packager keeps them apart and expects a player to pair them.

Nothing is re-encoded. Samples are written in decoding order across the inputs, so the file is interleaved the way a player reads it rather than one whole track followed by another, which would make a player seek the length of the film to hear it.

An input carrying a track this package cannot describe is not fatal: that track is left out and named in the error only if nothing else remains, since losing a stray text track is better than losing the film with it.

func JoinProgressive

func JoinProgressive(w io.Writer, srcs []*Reader, opts ...RemuxOption) error

JoinProgressive is Join, writing the ordinary MP4 a file on disk should be rather than the fragmented one a player streams.

func OpusHead

func OpusHead(cfg TrackConfig) ([]byte, error)

OpusHead renders an Opus configuration as the identification header Matroska and Ogg carry, which is what a caller writing one of those needs back.

func Remux

func Remux(w io.Writer, src *Reader, opts ...RemuxOption) error

Remux copies every track of src into a fragmented MP4 on w: it reads each track's configuration and samples, declares them all, and writes them interleaved by decode time so a player can read the result in one pass. It is the loop a caller otherwise writes by hand, and the foundation of Cut and Concat.

Nothing is re-encoded and no sample's bytes are touched. What the output cannot carry over is timing the muxer does not express: each track's timeline restarts at zero and advances by its own sample durations, so edit lists and gaps in the input are not reproduced.

A track whose samples cannot be read fails the whole copy, because the reader cannot tell a deliberately empty track from an unreadable sample table; DropTracks is how a caller who knows better leaves it out.

Types

type ConfigOption

type ConfigOption func(*configSettings)

ConfigOption tunes how a configuration is derived.

func ConfigLanguage

func ConfigLanguage(language string) ConfigOption

ConfigLanguage states the track's ISO-639-2 language code, which no bitstream carries.

func SampleTimescale

func SampleTimescale(timescale uint32) ConfigOption

SampleTimescale states the unit the samples' durations are counted in, per second, which is carried straight into TrackConfig.Timescale. VP9 needs it for a second reason: a vpcC record has to state a level, and a level is the frame rate as much as the frame size, so a VP9 track derived without a timescale is refused rather than levelled by guess.

type File

type File struct {
	Format    string // "mp4" or "matroska" or "webm"
	Brand     string // MP4 major brand (e.g. "isom"); "" for Matroska
	Timescale uint32 // movie/segment timescale (units per second)
	Duration  uint64 // overall duration in Timescale units
	Tracks    []Track
}

File is a demuxed container's structure.

func Demux

func Demux(data []byte) (*File, error)

Demux sniffs data's container format and returns its demuxed structure. It returns an error for an unrecognised or malformed container.

func (*File) AudioTracks

func (f *File) AudioTracks() []Track

AudioTracks returns the file's audio tracks, in file order.

func (*File) DurationSeconds

func (f *File) DurationSeconds() float64

DurationSeconds returns the overall duration in seconds (0 if unknown).

func (*File) VideoTracks

func (f *File) VideoTracks() []Track

VideoTracks returns the file's video tracks, in file order.

type FileSource

type FileSource interface {
	io.ReadSeeker
	io.ReaderAt
}

Reader reads the samples of an MP4, progressive or fragmented, alongside the metadata Demux already reports.

It is the counterpart of Muxer: TrackConfig hands back exactly what AddTrack needs, and Samples hands back exactly what WriteSample takes, so a track can be copied from one file into another without re-encoding and without the caller knowing what a sample table is. FileSource is what a file offers a reader that does not hold it in memory: seeking, to walk the boxes, and reading at a position, to fetch one sample when it is asked for. An *os.File is one, and so is a bytes.Reader.

type Format

type Format uint8

Format identifies a container format.

const (
	FormatUnknown Format = iota
	FormatMP4
	FormatMatroska // MKV and WebM (both EBML/Matroska)
	FormatMPEGTS   // MPEG-2 transport stream, as HLS delivers it
)

func Sniff

func Sniff(data []byte) Format

Sniff identifies the container format from data's leading bytes.

type Kind

type Kind uint8

Kind classifies a media track.

const (
	Other Kind = iota
	Video
	Audio
	Subtitle
)

func (Kind) String

func (k Kind) String() string

String returns the track kind's lowercase name.

type MuxOption

type MuxOption func(*muxSettings)

MuxOption configures a Muxer.

func Brand

func Brand(b string) MuxOption

Brand sets the major brand written in ftyp.

func FragmentDuration

func FragmentDuration(d time.Duration) MuxOption

FragmentDuration sets how much media a fragment holds. A value of zero or less restores the default.

type Muxer

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

Muxer writes a fragmented MP4: an initialisation segment naming every track, then one fragment after another.

It is what joining separately delivered streams needs — a DASH presentation keeps video and audio apart — and it never re-encodes: samples are written as handed over.

func NewMuxer

func NewMuxer(w io.Writer, opts ...MuxOption) *Muxer

NewMuxer returns a Muxer writing to w.

func (*Muxer) AddTrack

func (m *Muxer) AddTrack(cfg TrackConfig) (uint32, error)

AddTrack declares a track and returns its identifier. Every track must be added before the first sample is written, because the initialisation segment names them all.

func (*Muxer) Close

func (m *Muxer) Close() error

Close writes what is left and refuses any further use. It reports an error when no track was ever declared, because that file would name nothing.

func (*Muxer) Flush

func (m *Muxer) Flush() error

Flush writes the buffered samples as one fragment. It does nothing when nothing is buffered.

func (*Muxer) WriteSample

func (m *Muxer) WriteSample(trackID uint32, s Sample) error

WriteSample appends one frame to a track. The initialisation segment is written before the first one, and a fragment is flushed once the first track holds a fragment's worth of media and reaches a sync sample.

type ProgressiveMuxer

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

ProgressiveMuxer writes a progressive — non-fragmented — MP4: ftyp, then one mdat holding every sample, then a moov whose sample tables address them.

It is the counterpart of Muxer, which writes the fragmented form a DASH or HLS presentation needs. A progressive file is what everything else expects: a player with no streaming stack, a hardware decoder, a tool that seeks by sample table. Neither re-encodes: samples are written as handed over.

Layout and memory

The sample tables state where each chunk of media sits in the file, so they cannot be written before the media. mdat therefore comes first and moov last, which is what lets the media data go straight out to the writer.

The media a muxer holds never exceeds MediaMemoryLimit, 256 MB by default, and a sample that would take it past that is refused with ErrMediaTooLarge rather than the muxer growing without end. What that limit has to cover depends on what the writer can do.

  • A writer that is also an io.Seeker — an os.File, say — is handed the media as it arrives, so what is held is one open chunk per track: the bound in practice is ChunkDuration of media per track, half a second by default, and the limit is only reached by a caller asking for chunks larger than it. The mdat of such a file carries a 64-bit size field, because its value is only known once every sample has been written and patching it must not move the media that follows.
  • Any other writer — a bytes.Buffer, a socket — cannot be sent an mdat header whose size is not known yet, so the media is held until Close and the whole file has to fit the limit. A file larger than that wants a writer that can seek.

The sample tables are held either way and cannot be bounded: moov has to list every sample. They cost some twenty bytes per sample, so two hours of 30 fps video beside one audio track is around 25 MB.

func NewProgressiveMuxer

func NewProgressiveMuxer(w io.Writer, opts ...ProgressiveOption) *ProgressiveMuxer

NewProgressiveMuxer returns a ProgressiveMuxer writing to w. When w can also seek, the media is written as it arrives; otherwise it is held until Close.

func (*ProgressiveMuxer) AddTrack

func (m *ProgressiveMuxer) AddTrack(cfg TrackConfig) (uint32, error)

AddTrack declares a track and returns its identifier. Every track must be added before the first sample is written, and every declared track must carry at least one sample: a progressive file whose first track has an empty sample table is indistinguishable from the initialisation segment of a fragmented one.

func (*ProgressiveMuxer) Close

func (m *ProgressiveMuxer) Close() error

Close writes what is left of the media, then the moov that addresses it, and refuses any further use.

func (*ProgressiveMuxer) WriteSample

func (m *ProgressiveMuxer) WriteSample(trackID uint32, s Sample) error

WriteSample appends one frame to a track. Frames are gathered into chunks and the chunks of every track are written in decode order, so a player reading the file front to back meets each track's media where it needs it.

The sample's data is not copied, so a caller writing out of a buffer it means to reuse has to hand over a slice of its own.

type ProgressiveOption

type ProgressiveOption func(*progressiveSettings)

ProgressiveOption configures a ProgressiveMuxer.

func ChunkDuration

func ChunkDuration(d time.Duration) ProgressiveOption

ChunkDuration sets how much of a track's media one chunk holds. A value of zero or less restores the default.

func MediaMemoryLimit

func MediaMemoryLimit(n uint64) ProgressiveOption

MediaMemoryLimit sets how much media data the muxer holds at once, which for a writer that cannot seek is the whole file. Zero restores the default, and a limit above what an mdat box with a 32-bit size can announce is lowered to it, since more than that could not be written out as one box.

func ProgressiveBrand

func ProgressiveBrand(b string) ProgressiveOption

ProgressiveBrand sets the major brand written in ftyp. An empty brand restores the default.

type Reader

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

func NewFileReader

func NewFileReader(src FileSource, size int64) (*Reader, error)

NewFileReader reads an MP4 that stays on disk: the sample tables are read, the media is not, and every sample is fetched when it is asked for. It is what reading a file larger than the memory to hand takes, and the price is one read per sample and a copy of it.

Only MP4 is served this way. A transport stream and a Matroska file are read in one pass by their nature — a demuxer walks them from end to end — so they are refused here rather than read twice as slowly.

func NewReader

func NewReader(data []byte) (*Reader, error)

NewReader reads the structure of a container and prepares its samples: an MP4's sample tables are indexed, and a transport stream or a Matroska file, which have none, is walked in one pass.

func NewSegmentedReader

func NewSegmentedReader(parts [][]byte) (*Reader, error)

NewSegmentedReader reads several streams that follow one another — the segments of an HLS playlist, say — as one logical stream.

It exists because a segment boundary is invisible in a concatenation. A transport stream ends its last packetised unit by simply stopping, so a demuxer given one long byte slice is still accumulating that unit when the next segment's tables arrive, and drops it: one access unit lost per join. Handing the segments over separately keeps them, and is what a caller that downloaded them one by one can do for free.

Tracks are matched across segments by identifier, and the configuration of the first segment that declares a track is the one kept: a stream that changes codec mid-playlist cannot be read as one track, and this reports what it can rather than pretending otherwise.

func OpenFile

func OpenFile(path string) (*Reader, func() error, error)

OpenFile reads a container from disk, choosing how by what it turns out to be: an MP4 stays on disk and its samples are fetched as they are asked for, while a transport stream or a Matroska file is read whole, because a demuxer walks those from end to end and there is nothing to address into.

The returned function closes what was opened. It is not nil.

func (*Reader) File

func (r *Reader) File() *File

File returns the container's metadata, as Demux reports it.

func (*Reader) Samples

func (r *Reader) Samples(trackID uint32) ([]Sample, error)

Samples reads every sample of a track, in decoding order.

func (*Reader) TrackConfig

func (r *Reader) TrackConfig(trackID uint32) (TrackConfig, error)

TrackConfig describes a track the way Muxer.AddTrack wants it, so a track can be copied without the caller reading a single box.

func (*Reader) TrackIDs

func (r *Reader) TrackIDs() []uint32

TrackIDs lists the tracks, in file order.

type RemuxOption

type RemuxOption func(*remuxSettings)

RemuxOption configures a remux operation.

func DropTracks

func DropTracks(trackIDs ...uint32) RemuxOption

DropTracks leaves the named input tracks out of the output — keeping the video and discarding a commentary track is this and nothing else. The identifiers are the input's own, as Reader.TrackIDs reports them, and one that names no track is an error rather than a silent no-op.

It is an option rather than an operation of its own so that it composes: tracks can be dropped while cutting or while concatenating.

func MuxOptions

func MuxOptions(opts ...MuxOption) RemuxOption

MuxOptions passes options on to the Muxer writing the output.

func RequireSyncStart

func RequireSyncStart() RemuxOption

RequireSyncStart makes Cut refuse a start that does not land exactly on a sync sample, instead of snapping back to the one before it. It is for a caller who would rather be told than handed a clip that begins earlier than asked.

type Sample

type Sample struct {
	// Data is the coded frame, in the length-prefixed form the container
	// expects for AVC and HEVC.
	Data []byte
	// Duration is how long the frame lasts, in the track's timescale.
	Duration uint32
	// CompositionOffset shifts presentation against decoding, for streams
	// that reorder frames.
	CompositionOffset int32
	// Sync marks a frame a player can start decoding at.
	Sync bool
}

Sample is one coded frame, in its track's own timescale.

type StreamConfig

type StreamConfig struct {
	TrackConfig

	// CodedWidth and CodedHeight are the frame size the bitstream codes,
	// which is a whole number of macroblocks or coding units and so is
	// usually larger than what is shown: 1080 lines of video are coded as
	// 1088 and cropped. TrackConfig.Width and Height hold the visible size,
	// the one a player displays and a sample entry states.
	CodedWidth, CodedHeight int
	// SARWidth and SARHeight are the sample (pixel) aspect ratio, 1:1 unless
	// the bitstream states otherwise. Display size is the visible size scaled
	// by this ratio, which is how anamorphic video ends up wider than it is
	// coded.
	SARWidth, SARHeight int
	// Profile, Tier and Level are the bitstream's own, as its headers code
	// them: profile_idc and level_idc for AVC, general_profile_idc,
	// general_tier_flag and general_level_idc for HEVC, seq_profile,
	// seq_tier_0 and seq_level_idx_0 for AV1, and the VP9 profile with the
	// level of the vpcC table. Tier is 0 for the codecs that have none.
	Profile, Tier, Level byte
	// ProfileCompatibility is the AVC constraint_set flags byte, or the HEVC
	// general_profile_compatibility_flags; 0 for the other codecs.
	ProfileCompatibility uint32
	// CodecString is the RFC 6381 codecs parameter of this track, such as
	// avc1.64000A, which is what an HLS or DASH manifest names it by.
	CodecString string
}

StreamConfig is what a track's own samples say about it.

The embedded TrackConfig is what Muxer.AddTrack and TSMuxer.AddTrack take, so deriving a configuration and writing the track is two calls. The remaining fields are what a manifest states and a sample entry does not: the coded frame size before cropping, the pixel aspect ratio, and the profile, tier and level.

func ConfigFromSamples

func ConfigFromSamples(codec string, samples []Sample, opts ...ConfigOption) (StreamConfig, error)

ConfigFromSamples derives a track's configuration from its coded samples.

codec is the sample entry the caller means to write — "avc1", "avc3", "hvc1", "hev1", "av01", "vp09" or "mp4a" — and it is taken as stated: the codec is never inferred from the sample data, because guessing AVC against HEVC from a NAL header byte misreads every unit of the stream. Samples that contradict the stated codec are reported as ErrCodecMismatch.

The samples are read in the form each codec's elementary stream has them: AVC and HEVC either start-code separated (Annex B, as MPEG-TS carries them) or four-byte length prefixed (as an MP4 sample holds them); AV1 as temporal units of OBUs; AAC as ADTS frames. Nothing is written back: the samples are read, never modified.

A codec whose configuration is not in its samples, or whose bitstream this cannot read soundly, is refused with ErrUnsupportedCodec rather than described by guess, because a wrong configuration is silent — the file plays as garbage.

type TSMuxer

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

TSMuxer writes an MPEG-2 transport stream: a program map naming every elementary stream, repeated often enough that a player joining part-way through can still make sense of what follows.

It is the counterpart of the Reader's transport stream side, and never re-encodes: samples are converted to the form a transport stream states them in — Annex-B for AVC and HEVC, ADTS for AAC — and written as handed over.

func NewTSMuxer

func NewTSMuxer(w io.Writer) *TSMuxer

NewTSMuxer returns a TSMuxer writing to w.

func (*TSMuxer) AddTrack

func (m *TSMuxer) AddTrack(cfg TrackConfig) (uint32, error)

AddTrack declares an elementary stream and returns its packet identifier, which is what a transport stream names a track by. Every track must be added before the first sample, because the program map describing them all is written in front of it.

func (*TSMuxer) Close

func (m *TSMuxer) Close() error

Close writes what a stream carrying nothing still owes — the tables naming its tracks — and refuses any further use. It reports an error when no track was ever declared, because that stream would describe nothing.

func (*TSMuxer) WriteSample

func (m *TSMuxer) WriteSample(trackID uint32, s Sample) error

WriteSample writes one frame as a packetised unit of its track. Each sample is a unit of its own, so the reader of the stream recovers exactly the samples that were written.

type Track

type Track struct {
	ID            uint32
	Kind          Kind
	Codec         string // fourcc / codec id, e.g. "avc1", "vp09", "mp4a", "V_VP9"
	Width, Height int    // video frame size in pixels (0 for non-video)
	Channels      int    // audio channel count (0 for non-audio)
	SampleRate    int    // audio sample rate in Hz (0 for non-audio)
	Timescale     uint32 // media timescale (units per second)
	Duration      uint64 // track duration in Timescale units
	Language      string // ISO-639-2 code, e.g. "und"
}

Track is one demuxed elementary stream's metadata.

func (Track) DurationSeconds

func (t Track) DurationSeconds() float64

DurationSeconds returns the track's duration in seconds (0 if unknown).

type TrackConfig

type TrackConfig struct {
	Kind Kind
	// Codec is the sample entry to write: "avc1", "avc3", "hvc1", "hev1",
	// "av01", "vp08", "vp09", "mjpg", "mp4a", "Opus", "ac-3", "ec-3".
	Codec string
	// Timescale is the unit of every duration of this track, per second.
	Timescale uint32
	// Width and Height are the frame size, for a video track.
	Width, Height int
	// Channels and SampleRate describe an audio track.
	Channels, SampleRate int
	// Language is an ISO-639-2 code; "und" when empty.
	Language string
	// SPS, PPS and VPS are the parameter sets of an AVC or HEVC track, as
	// raw NAL units without a start code.
	SPS, PPS, VPS [][]byte
	// CodecConfig is the codec configuration record of a track whose
	// parameters are not NAL units, such as the av1C payload of AV1.
	CodecConfig []byte
	// AudioObjectType selects the AAC profile; 0 means AAC-LC.
	AudioObjectType byte
	// PreSkip is how many samples an Opus decoder discards at the start of
	// the track, counted at 48 kHz whatever the input rate. A track written
	// without it starts a few milliseconds early. It is stated by the
	// identification header a Matroska or an Ogg file carries, so a caller
	// that passes CodecConfig does not need to fill this in.
	PreSkip uint16
	// VPx describes a VP8 or VP9 track. ISO-BMFF cannot carry one without
	// this record, and unlike AVC or HEVC the bitstream keeps it out of the
	// sample data, so a caller remuxing VP9 has to state it.
	VPx *VPxConfig
}

TrackConfig describes a track to write. What a codec needs to be described differs, so the fields a codec ignores may be left at zero.

type VPxConfig

type VPxConfig struct {
	// Profile is 0 to 3, and Level a value of the VP9 level table (10 for
	// level 1, 11 for 1.1, and so on). Level 0 is not a level: a record that
	// states it is refused rather than written as a guess.
	Profile, Level byte
	// BitDepth is 8, 10 or 12.
	BitDepth byte
	// ChromaSubsampling is 0 (4:2:0 vertically colocated), 1 (4:2:0
	// colocated), 2 (4:2:2) or 3 (4:4:4).
	ChromaSubsampling byte
	// FullRange tells a player the samples use the full range rather than
	// the studio swing of 16 to 235.
	FullRange bool
	// ColourPrimaries, TransferCharacteristics and MatrixCoefficients are
	// the ISO/IEC 23001-8 code points; 2 means unspecified.
	ColourPrimaries, TransferCharacteristics, MatrixCoefficients byte
}

VPxConfig is what the vpcC record says about a VP8 or VP9 track. The colour fields take their values from ISO/IEC 23001-8; leaving them at 2, the value that means "unspecified", is what a caller that does not know them should do.

type WebMMuxer

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

func NewWebMMuxer

func NewWebMMuxer(w io.Writer, opts ...WebMOption) *WebMMuxer

NewWebMMuxer returns a WebMMuxer writing to w. It writes to w as samples arrive and never closes it.

func (*WebMMuxer) AddTrack

func (m *WebMMuxer) AddTrack(cfg TrackConfig) (uint32, error)

AddTrack declares a track and returns its identifier, which is the Matroska track number. Every track must be added before the first sample is written, because the Tracks element names them all and precedes the first cluster.

A configuration this cannot describe is refused here rather than written as a guess: a track entry a player cannot decode from is worse than no file.

func (*WebMMuxer) Close

func (m *WebMMuxer) Close() error

Close finishes the file and refuses any further use. It reports an error when no track was ever declared, because that file would name nothing.

func (*WebMMuxer) WriteSample

func (m *WebMMuxer) WriteSample(trackID uint32, s Sample) error

WriteSample appends one frame to a track. The header and the track list are written before the first one in streaming mode, and every mode starts a new cluster when the open one has held a cluster's worth of media and the first track reaches a sync sample — which is what lets a player start there — or when the frame's timestamp no longer fits the 16 bits a block states it in.

Matroska blocks carry presentation times, not decoding times, so a sample's CompositionOffset is added to its time rather than written beside it. Samples are written in the order handed over: a caller interleaving several tracks keeps them in step, as it must for the fragmented MP4 muxer too.

type WebMOption

type WebMOption func(*webmSettings)

WebMOption configures a WebMMuxer.

func BufferedSegment

func BufferedSegment() WebMOption

BufferedSegment holds the whole segment in memory until Close, so it can be written with every size known and with the duration stated in Info. That is what a caller writing to a file wants; a caller writing to an HTTP response cannot afford it and should not ask for it.

func ClusterDuration

func ClusterDuration(d time.Duration) WebMOption

ClusterDuration sets how much media a cluster holds before the next sync sample of the first track starts a new one. A value of zero or less restores the default.

func TimestampScale

func TimestampScale(tick time.Duration) WebMOption

TimestampScale sets how long one segment tick lasts, which is the resolution every timestamp in the file is stated at. The default millisecond cannot state a 90 kHz or a 48 kHz time exactly, so a caller who needs the timestamps it hands over to survive unrounded asks for a finer tick here.

It is not free: a block states its timestamp relative to its cluster in a signed 16-bit field, so the finer the tick the shorter a cluster can be, and the more clusters the file holds.

A tick that is not a whole divisor of a second, or is longer than one, restores the default: the scale has to divide a second exactly or the ticks per second this converts with would themselves be rounded.

Jump to

Keyboard shortcuts

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