Skip to content

Commit

Permalink
refactor: support multiple kinds of outputs through an interface
Browse files Browse the repository at this point in the history
This renames the "unix" output to ALSA (which it really is), in
preparation of PulseAudio support.
  • Loading branch information
aykevl authored and devgianlu committed Nov 7, 2024
1 parent f2de517 commit 45ea5e8
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 60 deletions.
28 changes: 14 additions & 14 deletions output/driver_unix.go → output/driver-alsa.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const (
NumPeriods = 4 // number of periods requested
)

type output struct {
type alsaOutput struct {
channels int
sampleRate int
device string
Expand Down Expand Up @@ -54,8 +54,8 @@ type output struct {
err chan error
}

func newOutput(reader librespot.Float32Reader, sampleRate int, channels int, device string, mixer string, control string, initialVolume float32, externalVolume bool, externalVolumeUpdate *RingBuffer[float32]) (*output, error) {
out := &output{
func newAlsaOutput(reader librespot.Float32Reader, sampleRate int, channels int, device string, mixer string, control string, initialVolume float32, externalVolume bool, externalVolumeUpdate *RingBuffer[float32]) (*alsaOutput, error) {
out := &alsaOutput{
reader: reader,
channels: channels,
sampleRate: sampleRate,
Expand All @@ -80,11 +80,11 @@ func newOutput(reader librespot.Float32Reader, sampleRate int, channels int, dev
return out, nil
}

func (out *output) alsaError(name string, err C.int) error {
func (out *alsaOutput) alsaError(name string, err C.int) error {
return fmt.Errorf("ALSA error at %s: %s", name, C.GoString(C.snd_strerror(err)))
}

func (out *output) setupPcm() error {
func (out *alsaOutput) setupPcm() error {
cdevice := C.CString(out.device)
defer C.free(unsafe.Pointer(cdevice))
if err := C.snd_pcm_open(&out.pcmHandle, cdevice, C.SND_PCM_STREAM_PLAYBACK, 0); err < 0 {
Expand Down Expand Up @@ -189,7 +189,7 @@ func (out *output) setupPcm() error {
return nil
}

func (out *output) logParams(params *C.snd_pcm_hw_params_t) error {
func (out *alsaOutput) logParams(params *C.snd_pcm_hw_params_t) error {
var dir C.int

var rate C.uint
Expand Down Expand Up @@ -228,7 +228,7 @@ func (out *output) logParams(params *C.snd_pcm_hw_params_t) error {
return nil
}

func (out *output) outputLoop(pcmHandle *C.snd_pcm_t) error {
func (out *alsaOutput) outputLoop(pcmHandle *C.snd_pcm_t) error {
floats := make([]float32, out.channels*out.periodSize)

for {
Expand Down Expand Up @@ -306,7 +306,7 @@ func (out *output) outputLoop(pcmHandle *C.snd_pcm_t) error {
}
}

func (out *output) Pause() error {
func (out *alsaOutput) Pause() error {
out.lock.Lock()
defer out.lock.Unlock()

Expand All @@ -322,7 +322,7 @@ func (out *output) Pause() error {
return nil
}

func (out *output) Resume() error {
func (out *alsaOutput) Resume() error {
out.lock.Lock()
defer out.lock.Unlock()

Expand All @@ -337,7 +337,7 @@ func (out *output) Resume() error {
return nil
}

func (out *output) Drop() error {
func (out *alsaOutput) Drop() error {
out.lock.Lock()
defer out.lock.Unlock()

Expand All @@ -357,7 +357,7 @@ func (out *output) Drop() error {
return nil
}

func (out *output) DelayMs() (int64, error) {
func (out *alsaOutput) DelayMs() (int64, error) {
out.lock.Lock()
defer out.lock.Unlock()

Expand All @@ -373,7 +373,7 @@ func (out *output) DelayMs() (int64, error) {
return int64(frames) * 1000 / int64(out.sampleRate), nil
}

func (out *output) SetVolume(vol float32) {
func (out *alsaOutput) SetVolume(vol float32) {
if vol < 0 || vol > 1 {
panic(fmt.Sprintf("invalid volume value: %0.2f", vol))
}
Expand All @@ -392,12 +392,12 @@ func (out *output) SetVolume(vol float32) {
}
}

func (out *output) Error() <-chan error {
func (out *alsaOutput) Error() <-chan error {
// No need to lock here (out.err is only set in newOutput).
return out.err
}

func (out *output) Close() error {
func (out *alsaOutput) Close() error {
out.lock.Lock()
defer out.lock.Unlock()

Expand Down
4 changes: 2 additions & 2 deletions output/mixer_unix.go → output/mixer-alsa.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
log "github.com/sirupsen/logrus"
)

func (out *output) setupMixer() error {
func (out *alsaOutput) setupMixer() error {
if len(out.mixer) == 0 {
out.mixerEnabled = false
return nil
Expand Down Expand Up @@ -74,7 +74,7 @@ func (out *output) setupMixer() error {
return nil
}

func (out *output) waitForMixerEvents() {
func (out *alsaOutput) waitForMixerEvents() {
for !out.closed {
var res = C.snd_mixer_wait(out.mixerHandle, -1)
if out.closed {
Expand Down
64 changes: 24 additions & 40 deletions output/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,27 @@ import (
librespot "github.com/devgianlu/go-librespot"
)

type Output struct {
*output
type Output interface {
// Pause pauses the output.
Pause() error

// Resume resumes the output.
Resume() error

// Drop empties the audio buffer without waiting.
Drop() error

// DelayMs returns the output device delay in milliseconds.
DelayMs() (int64, error)

// SetVolume sets the volume (0-1).
SetVolume(vol float32)

// Error returns the error that stopped the device (if any).
Error() <-chan error

// Close closes the output.
Close() error
}

type NewOutputOptions struct {
Expand Down Expand Up @@ -52,46 +71,11 @@ type NewOutputOptions struct {
ExternalVolumeUpdate *RingBuffer[float32]
}

func NewOutput(options *NewOutputOptions) (*Output, error) {
out, err := newOutput(options.Reader, options.SampleRate, options.ChannelCount, options.Device, options.Mixer, options.Control, options.InitialVolume, options.ExternalVolume, options.ExternalVolumeUpdate)
func NewOutput(options *NewOutputOptions) (Output, error) {
out, err := newAlsaOutput(options.Reader, options.SampleRate, options.ChannelCount, options.Device, options.Mixer, options.Control, options.InitialVolume, options.ExternalVolume, options.ExternalVolumeUpdate)
if err != nil {
return nil, err
}

return &Output{out}, nil
}

// Pause pauses the output.
func (c *Output) Pause() error {
return c.output.Pause()
}

// Resume resumes the output.
func (c *Output) Resume() error {
return c.output.Resume()
}

// Drop empties the audio buffer without waiting.
func (c *Output) Drop() error {
return c.output.Drop()
}

// DelayMs returns the output device delay in milliseconds.
func (c *Output) DelayMs() (int64, error) {
return c.output.DelayMs()
}

// SetVolume sets the volume (0-1).
func (c *Output) SetVolume(vol float32) {
c.output.SetVolume(vol)
}

// Error returns the error that stopped the device (if any).
func (c *Output) Error() <-chan error {
return c.output.Error()
}

// Close closes the output.
func (c *Output) Close() error {
return c.output.Close()
return out, nil
}
7 changes: 3 additions & 4 deletions player/player.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type Player struct {
sp *spclient.Spclient
audioKey *audio.KeyProvider

newOutput func(source librespot.Float32Reader, volume float32) (*output.Output, error)
newOutput func(source librespot.Float32Reader, volume float32) (output.Output, error)

cmd chan playerCmd
ev chan Event
Expand Down Expand Up @@ -70,8 +70,7 @@ func NewPlayer(sp *spclient.Spclient, audioKey *audio.KeyProvider, normalisation
normalisationEnabled: normalisationEnabled,
normalisationPregain: normalisationPregain,
countryCode: countryCode,
volumeSteps: volumeSteps,
newOutput: func(reader librespot.Float32Reader, volume float32) (*output.Output, error) {
newOutput: func(reader librespot.Float32Reader, volume float32) (output.Output, error) {
return output.NewOutput(&output.NewOutputOptions{
Reader: reader,
SampleRate: SampleRate,
Expand All @@ -96,7 +95,7 @@ func NewPlayer(sp *spclient.Spclient, audioKey *audio.KeyProvider, normalisation

func (p *Player) manageLoop() {
// currently available output device
var out *output.Output
var out output.Output
outErr := make(<-chan error)

// initial volume is 1
Expand Down

0 comments on commit 45ea5e8

Please sign in to comment.