oto

package module
v2.4.2 Latest Latest
Warning

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

Go to latest
Published: Sep 24, 2023 License: Apache-2.0 Imports: 8 Imported by: 64

README

Oto (v2)

Go Reference

A low-level library to play sound.

Platforms

  • Windows (no Cgo required!)
  • macOS (no Cgo required!)
  • Linux
  • FreeBSD
  • OpenBSD
  • Android
  • iOS
  • WebAssembly
  • Nintendo Switch
  • Xbox

Prerequisite

On some platforms you will need a C/C++ compiler in your path that Go can use.

  • iOS: On newer macOS versions type clang on your terminal and a dialog with installation instructions will appear if you don't have it
    • If you get an error with clang use xcode instead xcode-select --install
  • Linux and other Unix systems: Should be installed by default, but if not try GCC or Clang
macOS

Oto requires AudioToolbox.framework, but this is automatically linked.

iOS

Oto requires these frameworks:

  • AVFoundation.framework
  • AudioToolbox.framework

Add them to "Linked Frameworks and Libraries" on your Xcode project.

Linux

ALSA is required. On Ubuntu or Debian, run this command:

apt install libasound2-dev

On RedHat-based linux distributions, run:

dnf install alsa-lib-devel

In most cases this command must be run by root user or through sudo command.

FreeBSD, OpenBSD

BSD systems are not tested well. If ALSA works, Oto should work.

Usage

The two main components of Oto are a Context and Players. The context handles interactions with the OS and audio drivers, and as such there can only be one context in your program.

From a context you can create any number of different players, where each player is given an io.Reader that it reads bytes representing sounds from and plays.

Note that a single io.Reader must not be used by multiple players.

Playing sounds from memory

The following is an example of loading and playing an MP3 file:

package main

import (
    "time"
    "os"

    "github.com/hajimehoshi/go-mp3"
    "github.com/hajimehoshi/oto/v2"
)

func main() {
    // Read the mp3 file into memory
    fileBytes, err := os.ReadFile("./my-file.mp3")
    if err != nil {
        panic("reading my-file.mp3 failed: " + err.Error())
    }

    // Convert the pure bytes into a reader object that can be used with the mp3 decoder
    fileBytesReader := bytes.NewReader(fileBytes)

    // Decode file
    decodedMp3, err := mp3.NewDecoder(fileBytesReader)
    if err != nil {
        panic("mp3.NewDecoder failed: " + err.Error())
    }

    // Prepare an Oto context (this will use your default audio device) that will
    // play all our sounds. Its configuration can't be changed later.

    // Usually 44100 or 48000. Other values might cause distortions in Oto
    samplingRate := 44100

    // Number of channels (aka locations) to play sounds from. Either 1 or 2.
    // 1 is mono sound, and 2 is stereo (most speakers are stereo). 
    numOfChannels := 2

    // Bytes used by a channel to represent one sample. Either 1 or 2 (usually 2).
    audioBitDepth := 2

    // Remember that you should **not** create more than one context
    otoCtx, readyChan, err := oto.NewContext(samplingRate, numOfChannels, audioBitDepth)
    if err != nil {
        panic("oto.NewContext failed: " + err.Error())
    }
    // It might take a bit for the hardware audio devices to be ready, so we wait on the channel.
    <-readyChan

    // Create a new 'player' that will handle our sound. Paused by default.
    player := otoCtx.NewPlayer(decodedMp3)
    
    // Play starts playing the sound and returns without waiting for it (Play() is async).
    player.Play()

    // We can wait for the sound to finish playing using something like this
    for player.IsPlaying() {
        time.Sleep(time.Millisecond)
    }

    // Now that the sound finished playing, we can restart from the beginning (or go to any location in the sound) using seek
    // newPos, err := player.(io.Seeker).Seek(0, io.SeekStart)
    // if err != nil{
    //     panic("player.Seek failed: " + err.Error())
    // }
    // println("Player is now at position:", newPos)
    // player.Play()

    // If you don't want the player/sound anymore simply close
    err = player.Close()
    if err != nil {
        panic("player.Close failed: " + err.Error())
    }
}
Playing sounds by file streaming

The above example loads the entire file into memory and then plays it. This is great for smaller files but might be an issue if you are playing a long song since it would take too much memory and too long to load.

In such cases you might want to stream the file. Luckily this is very simple, just use os.Open:

package main

import (
    "bytes"
    "os"
    "time"

    "github.com/hajimehoshi/go-mp3"
    "github.com/hajimehoshi/oto/v2"
)

func main() {
    // Open the file for reading. Do NOT close before you finish playing!
    file, err := os.Open("./my-file.mp3")
    if err != nil {
        panic("opening my-file.mp3 failed: " + err.Error())
    }

    // Decode file. This process is done as the file plays so it won't
    // load the whole thing into memory.
    decodedMp3, err := mp3.NewDecoder(file)
    if err != nil {
        panic("mp3.NewDecoder failed: " + err.Error())
    }

    // Rest is the same...

    // Close file only after you finish playing
    file.Close()
}

The only thing to note about streaming is that the file object must be kept alive, otherwise you might just play static.

To keep it alive not only must you be careful about when you close it, but you might need to keep a reference to the original file object alive (by for example keeping it in a struct).

Advanced usage

Players have their own internal audio data buffer, so while for example 200 bytes have been read from the io.Reader that doesn't mean they were all played from the audio device.

Data is moved from io.Reader->internal buffer->audio device, and when the internal buffer moves data to the audio device is not guaranteed, so there might be a small delay. The amount of data in the buffer can be retrieved using Player.UnplayedBufferSize().

The size of the underlying buffer of a player can also be set by type-asserting the player object:

myPlayer.(oto.BufferSizeSetter).SetBufferSize(newBufferSize)

This works because players implement a Player interface and a BufferSizeSetter interface.

Crosscompiling

Crosscompiling to macOS or Windows is as easy as setting GOOS=darwin or GOOS=windows, respectively.

To crosscompile for other platforms, make sure the libraries for the target architecture are installed, and set CGO_ENABLED=1 as Go disables Cgo on crosscompiles by default.

Documentation

Index

Constants

View Source
const (
	// FormatFloat32LE is the format of 32 bits floats little endian.
	FormatFloat32LE = 0

	// FormatUnsignedInt8 is the format of 8 bits integers.
	FormatUnsignedInt8 = 1

	//FormatSignedInt16LE is the format of 16 bits integers little endian.
	FormatSignedInt16LE = 2
)

Variables

This section is empty.

Functions

This section is empty.

Types

type BufferSizeSetter added in v2.1.0

type BufferSizeSetter interface {
	// SetBufferSize sets the buffer size.
	// If 0 is specified, the default buffer size is used.
	SetBufferSize(bufferSize int)
}

BufferSizeSetter sets a buffer size. A player created by (*Context).NewPlayer implments both Player and BufferSizeSetter.

type Context

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

Context is the main object in Oto. It interacts with the audio drivers.

To play sound with Oto, first create a context. Then use the context to create an arbitrary number of players. Then use the players to play sound.

Creating multiple contexts is NOT supported.

func NewContext

func NewContext(sampleRate int, channelCount int, format int) (*Context, chan struct{}, error)

NewContext creates a new context, that creates and holds ready-to-use Player objects, and returns a context, a channel that is closed when the context is ready, and an error if it exists.

Creating multiple contexts is NOT supported.

The sampleRate argument specifies the number of samples that should be played during one second. Usual numbers are 44100 or 48000. One context has only one sample rate. You cannot play multiple audio sources with different sample rates at the same time.

The channelCount argument specifies the number of channels. One channel is mono playback. Two channels are stereo playback. No other values are supported.

The format argument specifies the format of sources. This value must be FormatFloat32LE, FormatUnsignedInt8, or FormatSignedInt16LE.

func NewContextWithOptions added in v2.4.0

func NewContextWithOptions(options *NewContextOptions) (*Context, chan struct{}, error)

NewContextWithOptions creates a new context with given options. A context creates and holds ready-to-use Player objects. NewContextWithOptions returns a context, a channel that is closed when the context is ready, and an error if it exists.

Creating multiple contexts is NOT supported.

func (*Context) Err added in v2.1.0

func (c *Context) Err() error

Err returns the current error.

Err is concurrent-safe.

func (*Context) NewPlayer

func (c *Context) NewPlayer(r io.Reader) Player

NewPlayer creates a new, ready-to-use Player belonging to the Context. It is safe to create multiple players.

The format of r is as follows:

[data]      = [sample 1] [sample 2] [sample 3] ...
[sample *]  = [channel 1] [channel 2] ...
[channel *] = [byte 1] [byte 2] ...

Byte ordering is little endian.

A player has some amount of an underlying buffer. Read data from r is queued to the player's underlying buffer. The underlying buffer is consumed by its playing. Then, r's position and the current playing position don't necessarily match. If you want to clear the underlying buffer for some reasons e.g., you want to seek the position of r, call the player's Reset function.

You cannot share r by multiple players.

The returned player implements Player, BufferSizeSetter, and io.Seeker. You can modify the buffer size of a player by the SetBufferSize function. A small buffer size is useful if you want to play a real-time PCM for example. Note that the audio quality might be affected if you modify the buffer size.

If r does not implement io.Seeker, the returned player's Seek returns an error.

NewPlayer is concurrent-safe.

All the functions of a Player returned by NewPlayer are concurrent-safe.

func (*Context) Resume

func (c *Context) Resume() error

Resume resumes the entire audio play, which was suspended by Suspend.

Resume is concurrent-safe.

func (*Context) Suspend

func (c *Context) Suspend() error

Suspend suspends the entire audio play.

Suspend is concurrent-safe.

type NewContextOptions added in v2.4.0

type NewContextOptions struct {
	// SampleRate specifies the number of samples that should be played during one second.
	// Usual numbers are 44100 or 48000. One context has only one sample rate. You cannot play multiple audio
	// sources with different sample rates at the same time.
	SampleRate int

	// ChannelCount specifies the number of channels. One channel is mono playback. Two
	// channels are stereo playback. No other values are supported.
	ChannelCount int

	// Format specifies the format of sources.
	// This value must be FormatFloat32LE, FormatUnsignedInt8, or FormatSignedInt16LE.
	Format int

	// BufferSize specifies a buffer size in the underlying device.
	//
	// If 0 is specified, the driver's default buffer size is used.
	// Set BufferSize to adjust the buffer size if you want to adjust latency or reduce noises.
	// Too big buffer size can increase the latency time.
	// On the other hand, too small buffer size can cause glitch noises due to buffer shortage.
	BufferSize time.Duration
}

NewContextOptions represents options for NewContextWithOptions.

type Player

type Player interface {
	// Pause pauses its playing.
	Pause()

	// Play starts its playing if it doesn't play.
	Play()

	// IsPlaying reports whether this player is playing.
	IsPlaying() bool

	// Reset clears the underyling buffer and pauses its playing.
	// Deprecated: use Pause or Seek instead.
	Reset()

	// Volume returns the current volume in the range of [0, 1].
	// The default volume is 1.
	Volume() float64

	// SetVolume sets the current volume in the range of [0, 1].
	SetVolume(volume float64)

	// UnplayedBufferSize returns the byte size in the underlying buffer that is not played yet.
	UnplayedBufferSize() int

	// Err returns an error if this player has an error.
	Err() error

	io.Closer
}

Player is a PCM (pulse-code modulation) audio player.

Directories

Path Synopsis
internal
mux
Package mux offers APIs for a low-level multiplexer of audio players.
Package mux offers APIs for a low-level multiplexer of audio players.

Jump to

Keyboard shortcuts

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