Documentation
¶
Overview ¶
Package aac implements an AAC-LC encoder ported from FFmpeg's native encoder, pinned at FFmpeg commit d09d5afc3a. See PROVENANCE.md for the origin and licensing of the ported code; architecture, porting and validation documentation is maintained in the internal planning repository.
The library has two layers, mirroring go-flac (flac + pcm) and go-opus (opus + oggopus):
- This package is the low-level codec: planar float32 frames in, raw AAC access units out (Encoder, EncodeFrame), plus the framing helpers raw-AU consumers need (AppendADTSHeader, Encoder.AudioSpecificConfig). Raw access units are not self-framing; see EncodeFrame.
- Package pcm is the streaming layer and the right entry point for almost all callers: interleaved little-endian integer PCM in via io.Writer, a self-framing ADTS stream out, or raw access units through a callback for muxing (pcm.FrameEncoder).
An Encoder is not safe for concurrent use; use one per goroutine. The package has no mutable global state, so any number of encoders run in parallel.
Example (LowLevel) ¶
Example_lowLevel encodes planar float32 frames with the low-level Encoder and frames each raw access unit in ADTS by hand. Callers with interleaved integer PCM should use the pcm package instead, which does all of this internally.
package main
import (
"fmt"
"log"
aac "github.com/tphakala/go-aac"
)
func main() {
enc, err := aac.NewEncoder(aac.EncoderConfig{
SampleRate: 48000,
Channels: 1,
Bitrate: 96000,
})
if err != nil {
log.Fatal(err)
}
frame := make([]float32, aac.FrameSize) // silence; real callers fill [-1, 1] samples
var stream, au []byte
writeAU := func() {
if len(au) == 0 {
return // encoder priming: the first call emits nothing
}
// A raw access unit is NOT self-framing: it must be wrapped in an
// ADTS header (or muxed into MP4 using AudioSpecificConfig).
stream, err = aac.AppendADTSHeader(stream, 48000, 1, len(au))
if err != nil {
log.Fatal(err)
}
stream = append(stream, au...)
}
for range 10 {
if au, err = enc.EncodeFrame(au[:0], [][]float32{frame}); err != nil {
log.Fatal(err)
}
writeAU()
}
for !enc.Drained() {
if au, err = enc.EncodeFrame(au[:0], nil); err != nil {
log.Fatal(err)
}
writeAU()
}
fmt.Printf("ASC % x, stream starts % x\n", enc.AudioSpecificConfig(), stream[:2])
}
Output: ASC 11 88, stream starts ff f1
Index ¶
Examples ¶
Constants ¶
const DefaultBitrate = 200_000
DefaultBitrate is the ABR target selected when a config leaves Bitrate zero: 200 kb/s for the whole stream, regardless of channel count. Mirrors AV_CODEC_DEFAULT_BITRATE, the bit_rate FFmpeg's encoder receives when the caller sets none (libavcodec/options_table.h:47 @ d09d5afc3a).
It is exported so a caller that has to predict the encoded size, such as a muxer pre-sizing a buffer, can read the value that will actually be used instead of restating it and hoping it does not drift.
const EncoderDelay = FrameSize
EncoderDelay is the encoder priming delay: the number of leading samples, per channel and at the output sample rate, that the decoded output carries ahead of the first input sample. A muxer must trim exactly this many leading samples from the decoded audio to reproduce the source gaplessly. For an MP4 edit list whose media timescale is the sample rate (the usual audio convention) set the entry's media_time to EncoderDelay; with a different timescale scale it accordingly, since media_time is in timescale units while EncoderDelay is in samples. For an iTunSMPB tag the priming (encoder delay) field is EncoderDelay, always in samples.
The delay is structurally one full frame, because the encoder buffers the first frame before emitting any access unit (see EncodeFrame). It is a property of go-aac's AAC-LC encoding path, not of AudioSpecificConfig, and it describes only the samples this encoder prepends; it makes no claim about any additional latency a particular decoder may add to its own output.
const FrameSize = 1024
FrameSize is the number of samples per channel consumed by one Encoder.EncodeFrame call and covered by one AAC access unit.
Variables ¶
var ( // ErrInvalidAudio reports input samples containing NaN or Inf. It maps // the C encoder's spectral coefficient guard (aacenc.c:1119-1124 // @ d09d5afc3a) to a testable sentinel. ErrInvalidAudio = errors.New("go-aac: input contains NaN or Inf") // ErrEncoderClosed is returned by pcm.Encoder.Write after Close and by // pcm.FrameEncoder.EncodeInterleaved after Flush; Close and Flush are // themselves idempotent and re-report the outcome of the flush they // performed, so neither returns this error on a stream that ended // cleanly. It is also returned by the aac.Encoder, pcm.Encoder and // pcm.FrameEncoder methods when the encoder is uninitialized: a zero // value, or one left unusable by a failed Reset. ErrEncoderClosed = errors.New("go-aac: encoder is closed") )
Sentinel errors returned by the aac and pcm packages, testable with errors.Is. They live in the root package, mirroring go-flac's layout (flac.ErrEncoderClosed is used by go-flac/pcm the same way).
var SampleRates = [13]int{96000, 88200, 64000, 48000, 44100, 32000,
24000, 22050, 16000, 12000, 11025, 8000, 7350}
SampleRates is the MPEG-4 audio sample rate table; the position of a rate is its 4-bit index in ADTS and AudioSpecificConfig. Mirrors ff_mpeg4audio_sample_rates (libavcodec/mpeg4audio.c @ d09d5afc3a).
Functions ¶
func AppendADTSHeader ¶
AppendADTSHeader appends the 7-byte ADTS header framing one raw AAC-LC access unit of payloadLen bytes, returning the extended slice. Callers of the low-level Encoder use it to build a streamable ADTS stream: for each access unit, append the header, then the access unit itself (the pcm package does exactly this). channels is the ADTS channel configuration (1..7). Byte-identical to FFmpeg's ADTS muxer output (libavformat/adtsenc.c:adts_write_frame_header @ d09d5afc3a).
Types ¶
type Coder ¶
type Coder int
Coder selects the quantizer search strategy. The zero value is CoderNMR, upstream's default at the pinned commit. Mirrors enum AACCoder (libavcodec/aacenc.h @ d09d5afc3a).
The constants are not ordered by speed, because speed is content dependent: CoderTwoLoop is faster than CoderNMR on broadband material but several times slower on tonal material. See each constant for the measurements.
The coders also code to different bandwidths when Cutoff is 0, and neither is consistently the wider one. CoderNMR takes FFmpeg's tuned rate-to-bandwidth table above 32 kb/s per channel, while CoderTwoLoop and CoderFast derive the cutoff from the bitrate alone (aacenc.c:1592-1614 @ d09d5afc3a). At 48 kHz mono that yields, in Hz:
per channel 32k 64k 128k 192k CoderNMR 14000 16000 18666 20000 TwoLoop/Fast 9500 16000 20000 22000
So a twoloop stream keeps more highs than an NMR stream at high bitrates but distinctly fewer at low ones, which is visible on a spectrogram as a different shelf. Set Cutoff explicitly if the coding bandwidth has to stay stable across coders.
const ( // CoderNMR is the noise-to-mask-ratio scalefactor trellis, and the // default here because it is the default upstream: aac_coder is // {.i64 = AAC_CODER_NMR} at aacenc.c:1651 @ d09d5afc3a. Upstream also // intends it to be the only coder, as the commit that made it the // default (0efac66e7e) states the old coders will soon be removed. // // FFmpeg's released documentation describes an older coder set in which // twoloop is the default and anmr is an experimental, lower quality // coder. That text is stale relative to the code at the pin (doc/ // encoders.texi does not mention the nmr option at all), and anmr is a // different coder from the nmr trellis ported here. CoderNMR Coder = iota // CoderTwoLoop is the ISO 13818-7 Appendix C two-loop search. It is // faster than CoderNMR on broadband material but several times slower // on tonal material, where the rate and distortion loops fight each // other and the search runs its full iteration budget every frame. // Encoding 20 s of 48 kHz mono at the default bitrate took 244 ms // against CoderNMR's 314 ms on broadband noise, but 1262 ms against // 336 ms on a 1400 Hz tone. CoderTwoLoop // CoderFast is the constrained two-loop heuristic, which skips the // more expensive adjustments. It was the fastest of the three on both // broadband and tonal material in the measurements above (126 ms and // 640 ms respectively). CoderFast )
Quantizer search strategies.
type Encoder ¶
type Encoder struct {
// contains filtered or unexported fields
}
Encoder is the low-level AAC-LC encoder: planar float32 frames in, raw access units out, one frame per call. The output is NOT self-framing; see EncodeFrame. For interleaved integer PCM in and a streamable ADTS stream out, use the pcm package instead.
An Encoder is stateful (MDCT overlap, psychoacoustic history, bit reservoir all carry across frames) and is not safe for concurrent use; use one Encoder per goroutine.
func NewEncoder ¶
func NewEncoder(cfg EncoderConfig) (*Encoder, error)
NewEncoder returns an Encoder for cfg. Mirrors aacenc.c:aac_encode_init @ d09d5afc3a: bitrate clamping, coding bandwidth selection and psychoacoustic model setup all happen here, fixed for the stream.
func (*Encoder) AudioSpecificConfig ¶
AudioSpecificConfig returns the MPEG-4 AudioSpecificConfig for this stream (2 bytes for AAC-LC), as carried in an MP4 esds box; raw-access- unit consumers hand it to their muxer or decoder. It returns a fresh copy on every call; callers may retain or mutate it freely. Mirrors aacenc.c:put_audio_specific_config @ d09d5afc3a.
func (*Encoder) Delay ¶
Delay reports this stream's encoder priming delay in samples per channel: the number of leading samples a muxer must trim from the decoded output. It feeds an MP4 edit list media_time (scaled to the track timescale, which for audio is usually the sample rate) or an iTunSMPB priming field, in samples. It always equals EncoderDelay for the current encoder, and is exposed as a method so a muxer holding an *Encoder can read the delay from the encoder itself. Should a future configuration ever make the delay depend on the stream, this accessor would keep reporting the correct value without any API change.
func (*Encoder) Drained ¶
Drained reports whether a drain (EncodeFrame with nil samples) has flushed all buffered audio; once true, further nil calls append nothing.
func (*Encoder) EncodeFrame ¶
EncodeFrame encodes the next frame of planar float32 PCM in [-1, 1] (one slice per channel, up to FrameSize samples each; only the final frame of a stream may be shorter, and it is zero-padded) and appends the encoded access unit to dst, returning the extended slice. It is append-style like strconv.AppendInt: allocation-free when dst has capacity.
The returned bytes are one RAW access unit, not a self-framing stream: concatenating raw access units produces a byte stream no decoder accepts. Wrap each access unit in an ADTS header (AppendADTSHeader), or mux the access units into a container (MP4/M4A) using AudioSpecificConfig for the decoder configuration. The pcm package is the right layer for almost all callers either way: pcm.Encoder does the ADTS framing automatically, and pcm.FrameEncoder reports raw access units for a muxer, both from interleaved integer PCM.
The encoder delays output by one frame (encoder priming): the first call appends nothing. This shifts the decoded output later by EncoderDelay samples per channel, which a muxer trims (see EncoderDelay). Pass nil samples to drain; each nil call appends one remaining access unit until Drained reports true:
for !e.Drained() {
dst, err = e.EncodeFrame(dst, nil)
}
Input containing NaN or Inf returns an error satisfying errors.Is(err, ErrInvalidAudio) and appends nothing. The samples are checked at ingest, so the error surfaces on the same call that carried the bad samples. After ErrInvalidAudio the stream is unusable; Reset the encoder.
func (*Encoder) Reset ¶
func (e *Encoder) Reset(cfg EncoderConfig) error
Reset re-arms the encoder for a new, independent stream with cfg, reusing all internal buffers (the encoder state is about 650 KiB, so pooled reuse matters; this is what backs pcm.EncodeInterleaved). Encoding after Reset produces the same bytes as a fresh NewEncoder. On error the encoder must not be used.
type EncoderConfig ¶
type EncoderConfig struct {
// SampleRate is the input sample rate in Hz: 44100 or 48000 in v1.
// Required; there is no zero default.
SampleRate int
// Channels is 1 (mono SCE) or 2 (stereo CPE). Required; there is no
// zero default.
Channels int
// Bitrate is the ABR target in bits per second for the whole stream
// (all channels), e.g. 128000. Zero selects DefaultBitrate. Targets above the AAC buffer model ceiling (6144
// bits per channel per 1024-sample frame) are clamped, exactly as the
// C encoder clamps them; negative targets are rejected.
Bitrate int
// Cutoff, when > 0, overrides the automatic coding bandwidth in Hz
// (FFmpeg -cutoff equivalent). It must not exceed SampleRate/2. Leave
// it 0 for the tuned rate-dependent default.
Cutoff int
// Coder selects the quantizer search. The zero value is CoderNMR,
// upstream's default at the pin and the recommended choice. See Coder
// for how the alternatives differ in speed and coding bandwidth; note
// that speed is content dependent rather than a fixed ordering.
Coder Coder
// Tool switches are negative (Disable*) so the zero value enables
// every tool, matching FFmpeg's defaults: TNS, PNS, M/S and I/S on.
DisableTNS bool // disable temporal noise shaping
DisablePNS bool // disable perceptual noise substitution
DisableMS bool // disable the mid/side stereo search
DisableIS bool // disable intensity stereo
}
EncoderConfig configures a low-level Encoder. It is a flat struct, mirroring opus.EncoderConfig in go-opus: every field's zero value is documented, so a literal with only SampleRate and Channels set is a complete, valid configuration.
type Stats ¶
type Stats struct {
Frames int64 // access units produced
ChannelFrames int64 // coded channel-frames (Frames x channels)
ShortFrames int64 // channel-frames coded with eight short windows
TNSLongFrames int64 // TNS-active channel-frames among long blocks
TNSShortFrames int64 // TNS-active channel-frames among short blocks
Bands int64 // coded channel-bands
PNSBands int64 // of which perceptual noise substitution
PairBands int64 // coded channel-pair bands (stereo only)
MSBands int64 // of which mid/side coded
ISBands int64 // of which intensity coded
MeanLambda float64 // mean per-frame operating lambda (the C's Qavg)
}
Stats reports encoder tool usage, accumulated per encoded frame over the final per-band decisions. It mirrors the counters FFmpeg's encoder prints once at uninit (libavcodec/aacenc.h:232-239, aacenc.c:1352-1386 @ d09d5afc3a); this library never logs, so the report is exposed as data instead. All counters reset on Encoder.Reset.
Directories
¶
| Path | Synopsis |
|---|---|
|
internal
|
|
|
bits
Package bits implements an MSB-first bit writer with the semantics of FFmpeg's PutBitContext (libavcodec/put_bits.h @ d09d5afc3a).
|
Package bits implements an MSB-first bit writer with the semantics of FFmpeg's PutBitContext (libavcodec/put_bits.h @ d09d5afc3a). |
|
coder
NMR (noise-to-mask ratio) scalefactor coder: an optimal Viterbi search over scalefactors with self rate control.
|
NMR (noise-to-mask ratio) scalefactor coder: an optimal Viterbi search over scalefactors with self rate control. |
|
dec
Package dec implements the AAC-LC decoder, ported from the fixed-point flavor of FFmpeg's AAC decoder (libavcodec/aac/ @ d09d5afc3a).
|
Package dec implements the AAC-LC decoder, ported from the fixed-point flavor of FFmpeg's AAC decoder (libavcodec/aac/ @ d09d5afc3a). |
|
dsp
Package dsp provides the scalar DSP kernels of the AAC encoder.
|
Package dsp provides the scalar DSP kernels of the AAC encoder. |
|
enc
Package enc implements the AAC encoder frame pipeline: input buffering, window switching, MDCT, psychoacoustic analysis, rate control and raw_data_block bitstream writing.
|
Package enc implements the AAC encoder frame pipeline: input buffering, window switching, MDCT, psychoacoustic analysis, rate control and raw_data_block bitstream writing. |
|
fdsp
Package fdsp ports the int32 primitives of libavutil/fixed_dsp.c that the fixed-point AAC decoder uses (avpriv_alloc_fixed_dsp @ d09d5afc3a).
|
Package fdsp ports the int32 primitives of libavutil/fixed_dsp.c that the fixed-point AAC decoder uses (avpriv_alloc_fixed_dsp @ d09d5afc3a). |
|
fmath
Package fmath centralizes the math primitives used by encoder code paths (docs/go-design.md: only this package imports math in per-frame code).
|
Package fmath centralizes the math primitives used by encoder code paths (docs/go-design.md: only this package imports math in per-frame code). |
|
mdct
Package mdct implements the forward MDCT used by the AAC encoder, numerically equivalent to FFmpeg's AV_TX_FLOAT_MDCT (libavutil/tx @ d09d5afc3a).
|
Package mdct implements the forward MDCT used by the AAC encoder, numerically equivalent to FFmpeg's AV_TX_FLOAT_MDCT (libavutil/tx @ d09d5afc3a). |
|
psy
Package psy implements the AAC encoder psychoacoustic model: the LAME block-switching window decision and the 3GPP TS26.403-inspired threshold analysis with its bit reservoir.
|
Package psy implements the AAC encoder psychoacoustic model: the LAME block-switching window decision and the 3GPP TS26.403-inspired threshold analysis with its bit reservoir. |
|
tables
SPDX-License-Identifier: LGPL-2.1-or-later
|
SPDX-License-Identifier: LGPL-2.1-or-later |
|
tx
Package tx ports the int32 flavor of FFmpeg's libavutil/tx transform framework, exactly as instantiated for AV_TX_INT32_MDCT by the fixed-point AAC decoder: the split-radix power-of-two FFT codelets and their fixed-point twiddle tables.
|
Package tx ports the int32 flavor of FFmpeg's libavutil/tx transform framework, exactly as instantiated for AV_TX_INT32_MDCT by the fixed-point AAC decoder: the split-radix power-of-two FFT codelets and their fixed-point twiddle tables. |
|
vlc
Package vlc implements a table-driven decoder for the variable-length codes of the AAC bitstream.
|
Package vlc implements a table-driven decoder for the variable-length codes of the AAC bitstream. |
|
window
Package window generates the AAC analysis half-windows at init time.
|
Package window generates the AAC analysis half-windows at init time. |
|
Package pcm is the high-level PCM streaming API for go-aac: interleaved little-endian integer PCM in, an AAC-LC ADTS stream out via io.Writer (Encoder), or raw access units out through a callback for muxing into MP4 (FrameEncoder).
|
Package pcm is the high-level PCM streaming API for go-aac: interleaved little-endian integer PCM in, an AAC-LC ADTS stream out via io.Writer (Encoder), or raw access units out through a callback for muxing into MP4 (FrameEncoder). |
|
tools
|
|
|
cdec/gencorpus
command
Generates D0 rehearsal corpus streams from the go-aac encoder itself: an ADTS stream and a raw-AU stream (2-byte BE length prefix per AU).
|
Generates D0 rehearsal corpus streams from the go-aac encoder itself: an ADTS stream and a raw-AU stream (2-byte BE length prefix per AU). |
|
cdec/gencraft
command
Crafts corpus streams for decode paths no encoder emits:
|
Crafts corpus streams for decode paths no encoder emits: |
|
wav2aac
command
wav2aac encodes a canonical RIFF/WAVE file to an ADTS AAC stream with go-aac.
|
wav2aac encodes a canonical RIFF/WAVE file to an ADTS AAC stream with go-aac. |