opus

package module
v1.0.1 Latest Latest
Warning

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

Go to latest
Published: May 16, 2025 License: MIT Imports: 10 Imported by: 0

README

Test

This is a fork of gopkg.in/hraban/opus.v2, modified to use a WASM build of libopus with wazero, removing the CGo dependency. The modified version is hosted at github.com/jj11hh/opus.

Go wrapper for Opus

This package provides a Go wrapper for the Opus audio codec, utilizing a WebAssembly (WASM) build of libopus executed via the wazero runtime. This approach eliminates the CGo dependency, making the library self-contained.

The C libraries and docs are hosted at https://opus-codec.org/. This package just handles the wrapping in Go, and is unaffiliated with xiph.org.

Features:

  • ✅ encode and decode raw PCM data to raw Opus data
  • ✅ useful when you control the recording device, and the playback
  • ✅ decode .opus and .ogg files into raw audio data ("PCM")
  • ✅ fully self-contained (no external libopus dependency needed)
  • ✅ works easily on Linux, Mac, Windows, and Docker (thanks to WASM)
  • ❌ does not create .opus or .ogg files (but feel free to send a PR)
  • ❌ does not work with .wav files (you need a separate .wav library for that)
  • ✅ self-contained binary (WASM build of libopus included)
  • ✅ cross-compiling is straightforward (CGo removed)

Good use cases:

  • 👍 you are writing a music player app in Go, and you want to play back .opus files
  • 👍 you record raw wav in a web app or mobile app, you encode it as Opus on the client, you send the opus to a remote webserver written in Go, and you want to decode it back to raw audio data on that server

Details

This wrapper interacts with a WASM build of the xiph.org opus library for:

  • encoders
  • decoders
  • files & streams

Import

import "github.com/jj11hh/opus"
// or, if you prefer to use the tagged version:
// import "github.com/jj11hh/opus/v1"

Encoding

To encode raw audio to the Opus format, create an encoder first:

const sampleRate = 48000
const channels = 1 // mono; 2 for stereo

enc, err := opus.NewEncoder(sampleRate, channels, opus.AppVoIP)
if err != nil {
    ...
}

Then pass it some raw PCM data to encode.

Make sure that the raw PCM data you want to encode has a legal Opus frame size. This means it must be exactly 2.5, 5, 10, 20, 40 or 60 ms long. The number of bytes this corresponds to depends on the sample rate (see the libopus documentation).

var pcm []int16 = ... // obtain your raw PCM data somewhere
const bufferSize = 1000 // choose any buffer size you like. 1k is plenty.

// Check the frame size. You don't need to do this if you trust your input.
frameSize := len(pcm) // must be interleaved if stereo
frameSizeMs := float32(frameSize) / channels * 1000 / sampleRate
switch frameSizeMs {
case 2.5, 5, 10, 20, 40, 60:
    // Good.
default:
    return fmt.Errorf("Illegal frame size: %d bytes (%f ms)", frameSize, frameSizeMs)
}

data := make([]byte, bufferSize)
n, err := enc.Encode(pcm, data)
if err != nil {
    ...
}
data = data[:n] // only the first N bytes are opus data. Just like io.Reader.

Note that you must choose a target buffer size, and this buffer size will affect the encoding process:

Size of the allocated memory for the output payload. This may be used to impose an upper limit on the instant bitrate, but should not be used as the only bitrate control. Use OPUS_SET_BITRATE to control the bitrate.

-- https://opus-codec.org/docs/opus_api-1.1.3/group__opus__encoder.html

Decoding

To decode opus data to raw PCM format, first create a decoder:

dec, err := opus.NewDecoder(sampleRate, channels)
if err != nil {
    ...
}

Now pass it the opus bytes, and a buffer to store the PCM sound in:

var frameSizeMs float32 = ...  // if you don't know, go with 60 ms.
frameSize := channels * frameSizeMs * sampleRate / 1000
pcm := make([]int16, int(frameSize))
n, err := dec.Decode(data, pcm)
if err != nil {
    ...
}

// To get all samples (interleaved if multiple channels):
pcm = pcm[:n*channels] // only necessary if you didn't know the right frame size

// or access sample per sample, directly:
for i := 0; i < n; i++ {
    ch1 := pcm[i*channels+0]
    // For stereo output: copy ch1 into ch2 in mono mode, or deinterleave stereo
    ch2 := pcm[(i*channels)+(channels-1)]
}

To handle packet loss from an unreliable network, see the DecodePLC and DecodeFEC options.

Streams (and Files)

To decode a .opus file (or .ogg with Opus data), or to decode a "Opus stream" (which is a Ogg stream with Opus data), use the Stream interface. It wraps an io.Reader providing the raw stream bytes and returns the decoded Opus data.

A crude example for reading from a .opus file:

f, err := os.Open(fname)
if err != nil {
    ...
}
s, err := opus.NewStream(f)
if err != nil {
    ...
}
defer s.Close()
pcmbuf := make([]int16, 16384)
for {
    n, err = s.Read(pcmbuf)
    if err == io.EOF {
        break
    } else if err != nil {
        ...
    }
    pcm := pcmbuf[:n*channels]

    // send pcm to audio device here, or write to a .wav file

}

See https://pkg.go.dev/github.com/jj11hh/opus#Stream for further info.

"My .ogg/.opus file doesn't play!" or "How do I play Opus in VLC / mplayer / ...?"

Note: this package only does encoding of your audio, to raw opus data. You can't just dump those all in one big file and play it back. You need extra info. First of all, you need to know how big each individual block is. Remember: opus data is a stream of encoded separate blocks, not one big stream of bytes. Second, you need meta-data: how many channels? What's the sampling rate? Frame size? Etc.

Look closely at the decoding sample code (not stream), above: we're passing all that meta-data in, hard-coded. If you just put all your encoded bytes in one big file and gave that to a media player, it wouldn't know what to do with it. It wouldn't even know that it's Opus data. It would just look like /dev/random.

What you need is a container format.

Compare it to video:

  • Encodings: MPEG[1234], VP9, H26[45], AV1
  • Container formats: .mkv, .avi, .mov, .ogv

For Opus audio, the most common container format is OGG, aka .ogg or .opus. You'll know OGG from OGG/Vorbis: that's Vorbis encoded audio in an OGG container. So for Opus, you'd call it OGG/Opus. But technically you could stick opus data in any container format that supports it, including e.g. Matroska (.mka for audio, you probably know it from .mkv for video).

Note: libopus, the C library that this wraps, technically comes with libopusfile, which can help with the creation of OGG/Opus streams from raw audio data. I just never needed it myself, so I haven't added the necessary code for it. If you find yourself adding it: send me a PR and we'll get it merged.

This libopus wrapper does come with code for decoding an OGG/Opus stream. Just not for writing one.

API Docs

Go wrapper API reference: https://pkg.go.dev/github.com/jj11hh/opus // or for v1.0.0: // https://pkg.go.dev/github.com/jj11hh/opus/v1

Full libopus C API reference: https://www.opus-codec.org/docs/opus_api-1.1.3/

For more examples, see the _test.go files.

Build & Installation

No external C library dependencies are required! This package embeds a WebAssembly (WASM) build of libopus and uses the wazero runtime to execute it. This means:

  • No need to install libopus-dev, libopusfile-dev, or pkg-config.
  • go build works out of the box.
  • Cross-compilation is simplified as there's no CGo.
  • Docker images don't need special apt-get or brew installs for Opus.

You can simply build your Go application:

go build

The sections below regarding libopusfile build tags, Docker configurations for C libraries, and linking libopus/libopusfile are no longer applicable due to the self-contained WASM approach.

License

The licensing terms for the Go bindings are found in the LICENSE file. The authors and copyright holders are listed in the AUTHORS file.

The copyright notice uses range notation to indicate all years in between are subject to copyright, as well. This statement is necessary, apparently. For all those nefarious actors ready to abuse a copyright notice with incorrect notation, but thwarted by a mention in the README. Pfew!

Documentation

Index

Constants

View Source
const (
	ErrOK             = Error(0)  // OPUS_OK
	ErrBadArg         = Error(-1) // OPUS_BAD_ARG
	ErrBufferTooSmall = Error(-2) // OPUS_BUFFER_TOO_SMALL
	ErrInternalError  = Error(-3) // OPUS_INTERNAL_ERROR
	ErrInvalidPacket  = Error(-4) // OPUS_INVALID_PACKET
	ErrUnimplemented  = Error(-5) // OPUS_UNIMPLEMENTED
	ErrInvalidState   = Error(-6) // OPUS_INVALID_STATE
	ErrAllocFail      = Error(-7) // OPUS_ALLOC_FAIL
)

Libopus errors using integer values corresponding to OPUS_*.

View Source
const (
	// AppVoIP is for voice over IP.
	AppVoIP = Application(2048) // OPUS_APPLICATION_VOIP
	// AppAudio is for general audio.
	AppAudio = Application(2049) // OPUS_APPLICATION_AUDIO
	// AppLowdelay is for low latency.
	AppRestrictedLowdelay = Application(2051) // OPUS_APPLICATION_RESTRICTED_LOWDELAY
)

Variables

This section is empty.

Functions

func CloseWasmContext

func CloseWasmContext(ctx context.Context) error

CloseWasmContext closes the global Wasm runtime. This should typically be called when the application exits.

func GetWasmContext

func GetWasmContext(ctx context.Context) (*wasmContext, error)

GetWasmContext returns the initialized global Wasm context. It will trigger initialization if not already done.

func Version

func Version() string

Types

type Application

type Application int

type Bandwidth

type Bandwidth int32
var (
	// Values will be initialized from Wasm
	Narrowband    Bandwidth
	Mediumband    Bandwidth
	Wideband      Bandwidth
	SuperWideband Bandwidth
	Fullband      Bandwidth
)

type Decoder

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

Decoder contains the state of an Opus decoder using WebAssembly.

func NewDecoder

func NewDecoder(sampleRate int, channels int) (*Decoder, error)

NewDecoder allocates a new Opus decoder and initializes it. wasmBinary is the []byte content of the opus.wasm file.

func (*Decoder) Decode

func (dec *Decoder) Decode(data []byte, pcm []int16) (int, error)

Decode encoded Opus data into the supplied int16 PCM buffer. Returns the number of decoded samples per channel.

func (*Decoder) DecodeFEC

func (dec *Decoder) DecodeFEC(data []byte, pcm []int16) (int, error)

DecodeFEC decodes a packet with FEC. pcm must be the size of the lost packet. Returns samples decoded per channel.

func (*Decoder) DecodeFECFloat32

func (dec *Decoder) DecodeFECFloat32(data []byte, pcm []float32) (int, error)

DecodeFECFloat32 decodes a packet with FEC. pcm must be the size of the lost packet. Returns samples decoded per channel.

func (*Decoder) DecodeFloat32

func (dec *Decoder) DecodeFloat32(data []byte, pcm []float32) (int, error)

DecodeFloat32 encoded Opus data into the supplied float32 PCM buffer. Returns the number of decoded samples per channel.

func (*Decoder) DecodePLC

func (dec *Decoder) DecodePLC(pcm []int16) (int, error)

DecodePLC recovers a lost packet using PLC. pcm must be the size of the lost packet. Returns samples decoded per channel.

func (*Decoder) DecodePLCFloat32

func (dec *Decoder) DecodePLCFloat32(pcm []float32) (int, error)

DecodePLCFloat32 recovers a lost packet using PLC. pcm must be the size of the lost packet. Returns samples decoded per channel.

func (*Decoder) Init

func (dec *Decoder) Init(sampleRate int, channels int) error

Init initializes a pre-allocated opus decoder.

func (*Decoder) LastPacketDuration

func (dec *Decoder) LastPacketDuration() (int, error)

LastPacketDuration gets the duration (in samples per channel) of the last successfully decoded/concealed packet.

type Encoder

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

Encoder contains the state of an Opus encoder using WebAssembly.

func NewEncoder

func NewEncoder(sampleRate int, channels int, application Application) (*Encoder, error)

NewEncoder allocates a new Opus encoder and initializes it. wasmBinary is the []byte content of the opus.wasm file.

func (*Encoder) Bitrate

func (enc *Encoder) Bitrate() (int, error)

Bitrate returns the bitrate of the Encoder.

func (*Encoder) Complexity

func (enc *Encoder) Complexity() (int, error)

Complexity returns the computational complexity used by the encoder.

func (*Encoder) DTX

func (enc *Encoder) DTX() (bool, error)

DTX reports whether this encoder is configured to use discontinuous transmission (DTX).

func (*Encoder) Encode

func (enc *Encoder) Encode(pcm []int16, data []byte) (int, error)

Encode raw PCM data (int16) and store the result in the supplied buffer.

func (*Encoder) EncodeFloat32

func (enc *Encoder) EncodeFloat32(pcm []float32, data []byte) (int, error)

EncodeFloat32 raw PCM data (float32) and store the result.

func (*Encoder) InBandFEC

func (enc *Encoder) InBandFEC() (bool, error)

InBandFEC gets the encoder's configured inband forward error correction (FEC).

func (*Encoder) InDTX

func (enc *Encoder) InDTX() (bool, error)

InDTX returns whether the last encoded frame was either a comfort noise update or not encoded due to DTX.

func (*Encoder) MaxBandwidth

func (enc *Encoder) MaxBandwidth() (Bandwidth, error)

MaxBandwidth gets the encoder's configured maximum allowed bandpass.

func (*Encoder) PacketLossPerc

func (enc *Encoder) PacketLossPerc() (int, error)

PacketLossPerc gets the encoder's configured packet loss percentage.

func (*Encoder) Reset

func (enc *Encoder) Reset() error

Reset resets the codec state to be equivalent to a freshly initialized state.

func (*Encoder) SampleRate

func (enc *Encoder) SampleRate() (int, error)

SampleRate returns the encoder sample rate in Hz.

func (*Encoder) SetBitrate

func (enc *Encoder) SetBitrate(bitrate int) error

SetBitrate sets the bitrate of the Encoder.

func (*Encoder) SetBitrateToAuto

func (enc *Encoder) SetBitrateToAuto() error

SetBitrateToAuto allows the encoder to automatically set the bitrate.

func (*Encoder) SetBitrateToMax

func (enc *Encoder) SetBitrateToMax() error

SetBitrateToMax causes the encoder to use as much rate as it can.

func (*Encoder) SetComplexity

func (enc *Encoder) SetComplexity(complexity int) error

SetComplexity sets the encoder's computational complexity.

func (*Encoder) SetDTX

func (enc *Encoder) SetDTX(dtx bool) error

SetDTX configures the encoder's use of discontinuous transmission (DTX).

func (*Encoder) SetInBandFEC

func (enc *Encoder) SetInBandFEC(fec bool) error

SetInBandFEC configures the encoder's use of inband forward error correction (FEC).

func (*Encoder) SetMaxBandwidth

func (enc *Encoder) SetMaxBandwidth(maxBw Bandwidth) error

SetMaxBandwidth configures the maximum bandpass that the encoder will select automatically.

func (*Encoder) SetPacketLossPerc

func (enc *Encoder) SetPacketLossPerc(lossPerc int) error

SetPacketLossPerc configures the encoder's expected packet loss percentage.

func (*Encoder) SetVBR

func (enc *Encoder) SetVBR(vbr bool) error

SetVBR configures the encoder's use of variable bitrate (VBR).

func (*Encoder) SetVBRConstraint

func (enc *Encoder) SetVBRConstraint(constraint bool) error

SetVBRConstraint configures the encoder's use of constrained VBR.

func (*Encoder) VBR

func (enc *Encoder) VBR() (bool, error)

VBR reports whether this encoder is configured to use variable bitrate (VBR).

func (*Encoder) VBRConstraint

func (enc *Encoder) VBRConstraint() (bool, error)

VBRConstraint reports whether this encoder is configured to use constrained VBR.

type Error

type Error int

func (Error) Error

func (e Error) Error() string

Error string (in human readable format) for libopus errors using wazero.

type WasmFunctions

type WasmFunctions struct {
	// Common
	Malloc api.Function
	Free   api.Function

	// Encoder functions
	OpusEncoderGetSize             api.Function
	OpusEncoderInit                api.Function
	OpusEncode                     api.Function
	OpusEncodeFloat                api.Function
	BridgeEncoderSetDtx            api.Function
	BridgeEncoderGetDtx            api.Function
	BridgeEncoderGetInDtx          api.Function
	BridgeEncoderGetSampleRate     api.Function
	BridgeEncoderSetBitrate        api.Function
	BridgeEncoderGetBitrate        api.Function
	BridgeEncoderSetComplexity     api.Function
	BridgeEncoderGetComplexity     api.Function
	BridgeEncoderSetMaxBandwidth   api.Function
	BridgeEncoderGetMaxBandwidth   api.Function
	BridgeEncoderSetInbandFec      api.Function
	BridgeEncoderGetInbandFec      api.Function
	BridgeEncoderSetPacketLossPerc api.Function
	BridgeEncoderGetPacketLossPerc api.Function
	BridgeEncoderSetVbr            api.Function
	BridgeEncoderGetVbr            api.Function
	BridgeEncoderSetVbrConstraint  api.Function
	BridgeEncoderGetVbrConstraint  api.Function
	BridgeEncoderResetState        api.Function

	// Decoder functions
	OpusDecoderGetSize                 api.Function
	OpusDecoderInit                    api.Function
	OpusDecode                         api.Function
	OpusDecodeFloat                    api.Function
	BridgeDecoderGetLastPacketDuration api.Function

	// Constant getter functions
	GetOpusOkAddress                     api.Function
	GetOpusBadArgAddress                 api.Function
	GetOpusBufferTooSmallAddress         api.Function
	GetOpusInternalErrorAddress          api.Function
	GetOpusInvalidPacketAddress          api.Function
	GetOpusUnimplementedAddress          api.Function
	GetOpusInvalidStateAddress           api.Function
	GetOpusAllocFailAddress              api.Function
	GetOpusBandwidthNarrowbandAddress    api.Function
	GetOpusBandwidthMediumbandAddress    api.Function
	GetOpusBandwidthWidebandAddress      api.Function
	GetOpusBandwidthSuperWidebandAddress api.Function
	GetOpusBandwidthFullbandAddress      api.Function
	GetOpusAutoAddress                   api.Function
	GetOpusBitrateMaxAddress             api.Function
}

WasmFunctions holds cached an api.Function instances from the Wasm module.

Jump to

Keyboard shortcuts

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