opus

package module
v0.0.0-...-4421c70 Latest Latest
Warning

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

Go to latest
Published: Jul 20, 2022 License: MIT Imports: 9 Imported by: 1

README

Fork of github.com/hraban/opus

Go wrapper for Opus

This package provides Go bindings for the xiph.org C libraries libopus and libopusfile.

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.

Details

This wrapper provides a Go translation layer for three elements from the xiph.org opus libs:

  • encoders
  • decoders
  • files & streams

Import

import "git.gammaspectra.live/S.O.N.G/go-pus"

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://godoc.org/gopkg.in/hraban/opus.v2#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://godoc.org/gopkg.in/hraban/opus.v2

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

This package requires libopus and libopusfile development packages to be installed on your system. These are available on Debian based systems from aptitude as libopus-dev and libopusfile-dev, and on Mac OS X from homebrew.

They are linked into the app using pkg-config.

Debian, Ubuntu, ...:

sudo apt-get install pkg-config libopus-dev libopusfile-dev

Mac:

brew install pkg-config opus opusfile

Building Without libopusfile

This package can be built without libopusfile by using the build tag nolibopusfile. This enables the compilation of statically-linked binaries with no external dependencies on operating systems without a static libopusfile, such as Alpine Linux.

Note: this will disable all file and Stream APIs.

To enable this feature, add -tags nolibopusfile to your go build or go test commands:

# Build
go build -tags nolibopusfile ...

# Test
go test -tags nolibopusfile ./...

Using in Docker

If your Dockerized app has this library as a dependency (directly or indirectly), it will need to install the aforementioned packages, too.

This means you can't use the standard golang:*-onbuild images, because those will try to build the app from source before allowing you to install extra dependencies. Instead, try this as a Dockerfile:

# Choose any golang image, just make sure it doesn't have -onbuild
FROM golang:1

RUN apt-get update && apt-get -y install libopus-dev libopusfile-dev

# Everything below is copied manually from the official -onbuild image,
# with the ONBUILD keywords removed.

RUN mkdir -p /go/src/app
WORKDIR /go/src/app

CMD ["go-wrapper", "run"]
COPY . /go/src/app
RUN go-wrapper download
RUN go-wrapper install

For more information, see https://hub.docker.com/_/golang/.

Linking libopus and libopusfile

The opus and opusfile libraries will be linked into your application dynamically. This means everyone who uses the resulting binary will need those libraries available on their system. E.g. if you use this wrapper to write a music app in Go, everyone using that music app will need libopus and libopusfile on their system. On Debian systems the packages are called libopus0 and libopusfile0.

The "cleanest" way to do this is to publish your software through a package manager and specify libopus and libopusfile as dependencies of your program. If that is not an option, you can compile the dynamic libraries yourself and ship them with your software as seperate (.dll or .so) files.

On Linux, for example, you would need the libopus.so.0 and libopusfile.so.0 files in the same directory as the binary. Set your ELF binary's rpath to $ORIGIN (this is not a shell variable but elf magic):

patchelf --set-origin '$ORIGIN' your-app-binary

Now you can run the binary and it will automatically pick up shared library files from its own directory.

Wrap it all in a .zip, and ship.

I know there is a similar trick for Mac (involving prefixing the shared library names with ./, which is, arguably, better). And Windows... probably just picks up .dll files from the same dir by default? I don't know. But there are ways.

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 (
	// 4 kHz passband
	Narrowband = Bandwidth(C.OPUS_BANDWIDTH_NARROWBAND)
	// 6 kHz passband
	Mediumband = Bandwidth(C.OPUS_BANDWIDTH_MEDIUMBAND)
	// 8 kHz passband
	Wideband = Bandwidth(C.OPUS_BANDWIDTH_WIDEBAND)
	// 12 kHz passband
	SuperWideband = Bandwidth(C.OPUS_BANDWIDTH_SUPERWIDEBAND)
	// 20 kHz passband
	Fullband = Bandwidth(C.OPUS_BANDWIDTH_FULLBAND)
)
View Source
const (
	ErrOK             = Error(C.OPUS_OK)
	ErrBadArg         = Error(C.OPUS_BAD_ARG)
	ErrBufferTooSmall = Error(C.OPUS_BUFFER_TOO_SMALL)
	ErrInternalError  = Error(C.OPUS_INTERNAL_ERROR)
	ErrInvalidPacket  = Error(C.OPUS_INVALID_PACKET)
	ErrUnimplemented  = Error(C.OPUS_UNIMPLEMENTED)
	ErrInvalidState   = Error(C.OPUS_INVALID_STATE)
	ErrAllocFail      = Error(C.OPUS_ALLOC_FAIL)
)

Libopus errors

View Source
const (
	// Optimize encoding for VoIP
	AppVoIP = Application(C.OPUS_APPLICATION_VOIP)
	// Optimize encoding for non-voice signals like music
	AppAudio = Application(C.OPUS_APPLICATION_AUDIO)
	// Optimize encoding for low latency applications
	AppRestrictedLowdelay = Application(C.OPUS_APPLICATION_RESTRICTED_LOWDELAY)
)
View Source
const (
	ErrStreamFalse        = StreamError(C.OP_FALSE)
	ErrStreamEOF          = StreamError(C.OP_EOF)
	ErrStreamHole         = StreamError(C.OP_HOLE)
	ErrStreamRead         = StreamError(C.OP_EREAD)
	ErrStreamFault        = StreamError(C.OP_EFAULT)
	ErrStreamImpl         = StreamError(C.OP_EIMPL)
	ErrStreamInval        = StreamError(C.OP_EINVAL)
	ErrStreamNotFormat    = StreamError(C.OP_ENOTFORMAT)
	ErrStreamBadHeader    = StreamError(C.OP_EBADHEADER)
	ErrStreamVersion      = StreamError(C.OP_EVERSION)
	ErrStreamNotAudio     = StreamError(C.OP_ENOTAUDIO)
	ErrStreamBadPacked    = StreamError(C.OP_EBADPACKET)
	ErrStreamBadLink      = StreamError(C.OP_EBADLINK)
	ErrStreamNoSeek       = StreamError(C.OP_ENOSEEK)
	ErrStreamBadTimestamp = StreamError(C.OP_EBADTIMESTAMP)
)

Libopusfile errors. The names are copied verbatim from the libopusfile library.

Variables

This section is empty.

Functions

func Version

func Version() string

Types

type Application

type Application int

type Bandwidth

type Bandwidth int

type Decoder

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

func NewDecoder

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

NewDecoder allocates a new Opus decoder and initializes it with the appropriate parameters. All related memory is managed by the Go GC.

func (*Decoder) Decode

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

Decode encoded Opus data into the supplied buffer. On success, returns the number of samples correctly written to the target buffer.

func (*Decoder) DecodeFEC

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

DecodeFEC encoded Opus data into the supplied buffer with forward error correction.

It is to be used on the packet directly following the lost one. The supplied buffer needs to be exactly the duration of audio that is missing

When a packet is considered "lost", DecodeFEC can be called on the next packet in order to try and recover some of the lost data. The PCM needs to be exactly the duration of audio that is missing. `LastPacketDuration()` can be used on the decoder to get the length of the last packet. Note also that in order to use this feature the encoder needs to be configured with SetInBandFEC(true) and SetPacketLossPerc(x) options.

Note that DecodeFEC automatically falls back to PLC when no FEC data is available in the provided packet.

func (*Decoder) DecodeFECFloat32

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

DecodeFECFloat32 encoded Opus data into the supplied buffer with forward error correction. It is to be used on the packet directly following the lost one. The supplied buffer needs to be exactly the duration of audio that is missing

func (*Decoder) DecodeFloat32

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

DecodeFloat32 encoded Opus data into the supplied buffer. On success, returns the number of samples correctly written to the target buffer.

func (*Decoder) DecodePLC

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

DecodePLC recovers a lost packet using Opus Packet Loss Concealment feature.

The supplied buffer needs to be exactly the duration of audio that is missing. When a packet is considered "lost", `DecodePLC` and `DecodePLCFloat32` methods can be called in order to obtain something better sounding than just silence. The PCM needs to be exactly the duration of audio that is missing. `LastPacketDuration()` can be used on the decoder to get the length of the last packet.

This option does not require any additional encoder options. Unlike FEC, PLC does not introduce additional latency. It is calculated from the previous packet, not from the next one.

func (*Decoder) DecodePLCFloat32

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

DecodePLCFloat32 recovers a lost packet using Opus Packet Loss Concealment feature. The supplied buffer needs to be exactly the duration of audio that is missing.

func (*Decoder) Init

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

func (*Decoder) LastPacketDuration

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

LastPacketDuration gets the duration (in samples) of the last packet successfully decoded or concealed.

type Encoder

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

Encoder contains the state of an Opus encoder for libopus.

func NewEncoder

func NewEncoder(sampleRate int, channels int, application Application, writer io.Writer) (*Encoder, error)

NewEncoder allocates a new Opus encoder and initializes it with the appropriate parameters.

func (*Encoder) Bitrate

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

Bitrate returns the bitrate of the Encoder

func (*Encoder) Close

func (enc *Encoder) Close() error

Close finishes stream, then frees allocated resources

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) error

Encode raw PCM data

func (*Encoder) EncodeFloat32

func (enc *Encoder) EncodeFloat32(pcm []float32) error

EncodeFloat32 raw PCM data

func (*Encoder) FlushHeaders

func (enc *Encoder) FlushHeaders() error

FlushHeaders Write out the header now rather than wait for audio to begin.

func (*Encoder) InBandFEC

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

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

func (*Encoder) Init

func (enc *Encoder) Init(sampleRate int, channels int, application Application, writer io.Writer) error

Init initializes a pre-allocated opus encoder. Unless the encoder has been created using NewEncoder, this method must be called exactly once in the life-time of this object, before calling any other methods.

func (*Encoder) MaxBandwidth

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

MaxBandwidth gets the encoder's configured maximum allowed bandpass.

func (*Encoder) MuxingDelay

func (enc *Encoder) MuxingDelay() (time.Duration, error)

MuxingDelay returns the maximum container delay

func (*Encoder) PacketLossPerc

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

PacketLossPerc gets the encoder's configured packet loss percentage.

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 will allow 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. This can be useful for controlling the rate by adjusting the output buffer size.

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) SetMuxingDelay

func (enc *Encoder) SetMuxingDelay(delay time.Duration) error

SetMuxingDelay sets maximum container delay

func (*Encoder) SetPacketLossPerc

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

SetPacketLossPerc configures the encoder's expected packet loss percentage.

type Error

type Error int

func (Error) Error

func (e Error) Error() string

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

type Stream

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

Stream wraps a io.Reader in a decoding layer. It provides an API similar to io.Reader, but it provides raw PCM data instead of the encoded Opus data.

This is not the same as directly decoding the bytes on the io.Reader; opus streams are Ogg Opus audio streams, which package raw Opus data.

This wraps libopusfile. For more information, see the api docs on xiph.org:

https://www.opus-codec.org/docs/opusfile_api-0.7/index.html

func NewStream

func NewStream(read io.Reader) (*Stream, error)

NewStream creates and initializes a new stream. Don't call .Init() on this.

func (*Stream) Close

func (s *Stream) Close() error

func (*Stream) Init

func (s *Stream) Init(read io.Reader) error

Init initializes a stream with an io.Reader to fetch opus encoded data from on demand. Errors from the reader are all transformed to an EOF, any actual error information is lost. The same happens when a read returns succesfully, but with zero bytes.

func (*Stream) Read

func (s *Stream) Read(pcm []int16) (int, error)

Read a chunk of raw opus data from the stream and decode it. Returns the number of decoded samples per channel. This means that a dual channel (stereo) feed will have twice as many samples as the value returned.

Read may successfully read less bytes than requested, but it will never read exactly zero bytes succesfully if a non-zero buffer is supplied.

The number of channels in the output data must be known in advance. It is possible to extract this information from the stream itself, but I'm not motivated to do that. Feel free to send a pull request.

func (*Stream) ReadFloat32

func (s *Stream) ReadFloat32(pcm []float32) (int, error)

ReadFloat32 is the same as Read, but decodes to float32 instead of int16.

func (*Stream) ReadStereo

func (s *Stream) ReadStereo(pcm []int16) (int, error)

ReadStereo is the same as Read, but decodes to always two channels

func (*Stream) ReadStereoFloat32

func (s *Stream) ReadStereoFloat32(pcm []float32) (int, error)

ReadStereoFloat32 is the same as ReadStereo, but decodes to float32 instead of int16.

type StreamError

type StreamError int

StreamError represents an error from libopusfile.

func (StreamError) Error

func (i StreamError) Error() string

Jump to

Keyboard shortcuts

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