mp3

package module
v1.4.0 Latest Latest
Warning

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

Go to latest
Published: Sep 6, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

README

go-mp3

Go Reference

An MP3 decoder in pure Go based on PDMP3.

Installation

go get github.com/llehouerou/go-mp3

Usage

package main

import (
	"os"

	"github.com/llehouerou/go-mp3"
)

func main() {
	f, err := os.Open("audio.mp3")
	if err != nil {
		panic(err)
	}
	defer f.Close()

	d, err := mp3.NewDecoder(f)
	if err != nil {
		panic(err)
	}

	// d implements io.Reader and io.Seeker
	// Output is always 16-bit stereo (4 bytes per sample)
	// Use d.SampleRate() to get the sample rate
}

Output Accuracy

Decoded PCM meets ISO/IEC 11172-4 limited compliance against a reference decoder (mpg123). Output is not guaranteed to be identical bit-for-bit across versions: arithmetic changes may move the last bits of a sample. Do not golden-hash decoder output in your own tests. See docs/adr/0002.

Thread Safety

The Decoder is not safe for concurrent use. If you need to access the decoder from multiple goroutines (e.g., one goroutine reading audio for playback while another handles seeking from user input), you must synchronize access yourself.

Example: Safe Concurrent Access
type SafeDecoder struct {
	mu      sync.Mutex
	decoder *mp3.Decoder
}

func NewSafeDecoder(r io.Reader) (*SafeDecoder, error) {
	d, err := mp3.NewDecoder(r)
	if err != nil {
		return nil, err
	}
	return &SafeDecoder{decoder: d}, nil
}

func (s *SafeDecoder) Read(p []byte) (int, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.decoder.Read(p)
}

func (s *SafeDecoder) Seek(offset int64, whence int) (int64, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.decoder.Seek(offset, whence)
}

func (s *SafeDecoder) SeekToTime(t time.Duration) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.decoder.SeekToTime(t)
}

func (s *SafeDecoder) Position() time.Duration {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.decoder.Position()
}

Gapless Playback

MP3 encoders add silence at the start (encoder delay) and end (padding) of files. This library automatically detects LAME/Xing metadata and trims this silence for seamless playback.

Gapless is enabled by default. The decoder automatically:

  • Parses LAME/Xing headers from the first frame
  • Skips encoder delay samples at the start
  • Trims padding samples at the end
  • Reports the trimmed length via Length() and Duration()
// Gapless playback works automatically
d, err := mp3.NewDecoder(file)
if err != nil {
    panic(err)
}

// Length() returns the trimmed (actual audio) length
fmt.Printf("Duration: %v\n", d.Duration())
fmt.Printf("Samples: %d\n", d.SampleCount())

// Access gapless metadata if needed
if info := d.GaplessInfo(); info != nil {
    fmt.Printf("Encoder: %s\n", info.LAMEVersion)
    fmt.Printf("Delay: %d samples\n", info.TotalDelay())
    fmt.Printf("Padding: %d samples\n", info.TotalPadding())
}

// Get raw (untrimmed) length for comparison
fmt.Printf("Raw length: %d bytes\n", d.RawLength())
Disabling Gapless

To decode without trimming (original behavior):

opts := mp3.DecoderOptions{Gapless: false}
d, err := mp3.NewDecoderWithOptions(file, opts)
Supported Encoders

Gapless playback works with files encoded by:

  • LAME (all versions)
  • ffmpeg/libavcodec (Lavc)
  • Gogo and other LAME-compatible encoders

Files without LAME/Xing headers are decoded normally without trimming.

The lameinfo Package

For advanced use cases, the lameinfo package provides direct access to LAME/Xing header data:

import "github.com/llehouerou/go-mp3/lameinfo"

info, err := lameinfo.ParseFromReader(file)
if err == nil {
    fmt.Printf("Frame count: %d\n", info.FrameCount)
    fmt.Printf("Byte count: %d\n", info.ByteCount)
    fmt.Printf("VBR scale: %d\n", info.VBRScale)
    fmt.Printf("Encoder delay: %d\n", info.EncoderDelay)
    fmt.Printf("Encoder padding: %d\n", info.EncoderPadding)
}

Known Limitations

  • Not all files have LAME headers: Files without LAME/Xing metadata are decoded normally without gapless adjustment. On such files Length() and Duration() count the frames on first use, which reads the whole file once; with a Xing/Info header they are free.
  • Seeking requires an io.Seeker: Seek, SeekToSample, SeekToTime and Skip return ErrNotSeekable on a plain io.Reader. Length() and Duration() still work there when the file carries a Xing/Info header, as does gapless trimming.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrNotSeekable = errors.New("mp3: source is not seekable")

ErrNotSeekable is returned by the seeking methods when the source is a plain io.Reader. Length and Duration can still be available in that case, when the file carries a Xing/Info header.

Functions

This section is empty.

Types

type Decoder

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

A Decoder is a MP3-decoded stream.

Decoder decodes its underlying source on the fly.

A Decoder is not safe for concurrent use. If multiple goroutines need to access the same Decoder (e.g., one for playback and one for seeking), the caller must synchronize access with a mutex or similar mechanism.

func NewDecoder

func NewDecoder(r io.Reader) (*Decoder, error)

NewDecoder decodes the given io.Reader and returns a decoded stream.

The stream is always formatted as 16bit (little endian) 2 channels even if the source is single channel MP3. Thus, a sample always consists of 4 bytes.

By default, gapless playback is enabled. Use NewDecoderWithOptions to configure decoder behavior.

func NewDecoderWithOptions added in v1.2.0

func NewDecoderWithOptions(r io.Reader, opts DecoderOptions) (*Decoder, error)

NewDecoderWithOptions decodes the given io.Reader with the specified options.

The stream is always formatted as 16bit (little endian) 2 channels even if the source is single channel MP3. Thus, a sample always consists of 4 bytes.

func (*Decoder) BytesPerFrame

func (d *Decoder) BytesPerFrame() int64

BytesPerFrame returns the number of decoded bytes per MP3 frame. This is useful for calculating frame timing or positions.

func (*Decoder) Duration

func (d *Decoder) Duration() time.Duration

Duration returns the total duration of the audio stream. Returns -1 if the duration cannot be determined.

func (*Decoder) GaplessInfo added in v1.2.0

func (d *Decoder) GaplessInfo() *lameinfo.Info

GaplessInfo returns the LAME/Xing gapless metadata if available. Returns nil if no gapless metadata was found or gapless mode is disabled.

func (*Decoder) Length

func (d *Decoder) Length() int64

Length returns the total size in bytes.

It is free when the file carries a Xing/Info header. Without one, the first call walks the file's frame headers to count them.

Length returns -1 when the total size cannot be determined, i.e. when the source is neither seekable nor carrying a Xing/Info header.

func (*Decoder) Position

func (d *Decoder) Position() time.Duration

Position returns the current playback position as a time.Duration.

func (*Decoder) Progress

func (d *Decoder) Progress() float64

Progress returns the playback progress as a value between 0.0 and 1.0. Returns -1 if progress cannot be determined.

func (*Decoder) RawLength added in v1.2.0

func (d *Decoder) RawLength() int64

RawLength returns the total decoded bytes without gapless trimming. Returns -1 if the length cannot be determined. If gapless mode is not active, this returns the same value as Length().

func (*Decoder) Read

func (d *Decoder) Read(buf []byte) (int, error)

Read is io.Reader's Read.

func (*Decoder) Remaining

func (d *Decoder) Remaining() time.Duration

Remaining returns the remaining duration from the current position. Returns -1 if duration cannot be determined.

func (*Decoder) SampleCount

func (d *Decoder) SampleCount() int64

SampleCount returns the total number of samples (per channel). Returns -1 if the count cannot be determined.

func (*Decoder) SamplePosition

func (d *Decoder) SamplePosition() int64

SamplePosition returns the current position in samples (per channel). Each sample is 4 bytes (stereo 16-bit).

func (*Decoder) SampleRate

func (d *Decoder) SampleRate() int

SampleRate returns the sample rate like 44100.

Note that the sample rate is retrieved from the first frame.

func (*Decoder) Seek

func (d *Decoder) Seek(offset int64, whence int) (int64, error)

Seek is io.Seeker's Seek.

Seek returns ErrNotSeekable when the underlying source is not io.Seeker. The first seek on a file without a Xing header walks its frame headers to build an index; later seeks reuse it.

Note that seek uses a byte offset but samples are aligned to 4 bytes (2 channels, 2 bytes each). Be careful to seek to an offset that is divisible by 4 if you want to read at full sample boundaries.

func (*Decoder) SeekToSample

func (d *Decoder) SeekToSample(sample int64) error

SeekToSample seeks to the specified sample position. Returns an error if seeking is not supported. Negative positions are clamped to 0, positions beyond the end are clamped.

func (*Decoder) SeekToTime

func (d *Decoder) SeekToTime(t time.Duration) error

SeekToTime seeks to the specified absolute time position. Returns an error if seeking is not supported. Negative times are clamped to 0, times beyond duration are clamped to the end.

func (*Decoder) Skip

func (d *Decoder) Skip(delta time.Duration) error

Skip seeks relative to the current position by the specified duration. Positive values skip forward, negative values skip backward. Returns an error if seeking is not supported. The result is clamped to the valid range [0, Duration].

type DecoderOptions added in v1.2.0

type DecoderOptions struct {
	// Gapless enables gapless playback by trimming encoder delay and padding.
	// When true (default), the decoder reads LAME/Xing metadata and adjusts
	// the audio stream to remove encoder-added silence.
	Gapless bool
}

DecoderOptions configures the MP3 decoder behavior.

func DefaultDecoderOptions added in v1.2.0

func DefaultDecoderOptions() DecoderOptions

DefaultDecoderOptions returns options with gapless enabled.

Directories

Path Synopsis
internal
testaudio
Package testaudio synthesises MP3 files for tests.
Package testaudio synthesises MP3 files for tests.
Package lameinfo provides parsing for LAME/Xing VBR headers in MP3 files.
Package lameinfo provides parsing for LAME/Xing VBR headers in MP3 files.

Jump to

Keyboard shortcuts

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