• Stars
    star
    264
  • Rank 155,103 (Top 4 %)
  • Language
    Go
  • License
    MIT License
  • Created almost 10 years ago
  • Updated 5 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Go wrapper for libopus (golang)

Test

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.

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")
  • βœ… reuse the system libraries for opus decoding (libopus)
  • βœ… works easily on Linux, Mac and Docker; needs libs on Windows
  • ❌ 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)
  • ❌ no self-contained binary (you need the xiph.org libopus lib, e.g. through a package manager)
  • ❌ no cross compiling (because it uses CGo)

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 provides a Go translation layer for three elements from the xiph.org opus libs:

  • encoders
  • decoders
  • files & streams

Import

import "gopkg.in/hraban/opus.v2"

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!

More Repositories

1

tomono

Multi- To Mono-repository merge
CSS
842
star
2

mac-app-util

Fix .app programs installed by Nix on Mac
Common Lisp
88
star
3

cl-graph

Common Lisp library for manipulating graphs and running graph algorithms
Common Lisp
73
star
4

cl-containers

Containers Library for Common Lisp
Common Lisp
64
star
5

cl-markdown

A Common Lisp rewrite of Markdown
Common Lisp
46
star
6

metabang-bind

bind is let and much much more
Common Lisp
43
star
7

trivial-shell

A simple Common-Lisp interface to the underlying Operating System
Common Lisp
30
star
8

lrucache

Light-weight in-memory LRU (object) cache library for Go
Go
29
star
9

lift

LIsp Framework for Testing
Common Lisp
25
star
10

cl-nix-lite

Common Lisp module for Nix, without Quicklisp
Nix
25
star
11

cl-html-parse

A portable version of Franz's Opensource HTML Parser
HTML
20
star
12

log5

A Common Lisp logging framework organized around five things: categories, outputs, senders, messages and contexts
Common Lisp
12
star
13

cl-mathstats

An unordered collection of mathematical routines
Common Lisp
12
star
14

trivial-backtrace

Portable simple API to work with backtraces in Common Lisp
Common Lisp
12
star
15

trivial-http

trivial (really) HTTP library for Common Lisp -- probably better to use something else!
Common Lisp
8
star
16

outbound-rules

Reinventing the wheel; Content-Security-Policy
TypeScript
8
star
17

metatilities-base

metabang's core utilities... - Something to stand on
Common Lisp
7
star
18

git-hly

Git utilities
Common Lisp
7
star
19

metatilities

metabang's utilities... - Now where is that kitchen sink...
Common Lisp
7
star
20

nixos-images

Using GH Actions to build NixOS images
Nix
6
star
21

moptilities

compatibility layer for minor MOP implementation differences
Common Lisp
6
star
22

websockethub

Easy peasy chatrooms on your website or webapp
JavaScript
5
star
23

c-exceptions

Simple exceptions in C
C
5
star
24

trivial-timeout

portable and possibly dangerous timeout library for Common Lisp
Common Lisp
5
star
25

metacopy

flexibly shallow/deep copying library for Common Lisp
Common Lisp
4
star
26

tinaa

Tinaa is a flexible and general purpose Lisp documentation system.
Common Lisp
4
star
27

dynamic-classes

Classes how you want them, when you want them
Common Lisp
4
star
28

udep

Tiny dependency framework inspired by Make and Luigi
Shell
3
star
29

emacs-nix

Emacs from source using Nix
Nix
2
star
30

clnuplot

GNUplot in Common Lisp
Common Lisp
2
star
31

aoc-2022-cl-nix

Litmus test for lispPackagesLite in Nix
Common Lisp
2
star
32

secrets-trampoline

Bake a secret into a binary with execute-only permissions
Nix
2
star
33

nix-update-sources

Find all updatable git sources in a Nix scope
Nix
1
star
34

cl-pgp

Common Lisp implementation of the OpenPGP standard (RFC 4880)
Common Lisp
1
star
35

web-project-template

Initial setup for web projects using Typescript, sourcemaps and gulp
JavaScript
1
star