Skip to content

Commit 9066a23

Browse files
committed
feat: support more PCM formats with ALSA
Thanks to @pilollipietro, see #160
1 parent 214ef18 commit 9066a23

1 file changed

Lines changed: 133 additions & 5 deletions

File tree

output/driver-alsa.go

Lines changed: 133 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ type alsaOutput struct {
3838
periodCount int
3939
periodSize int
4040
bufferSize int
41+
pcmFormat C.snd_pcm_format_t
4142

4243
externalVolume bool
4344

@@ -100,6 +101,15 @@ func (out *alsaOutput) alsaError(name string, err C.int) error {
100101
return fmt.Errorf("ALSA error at %s: %s", name, C.GoString(C.snd_strerror(err)))
101102
}
102103

104+
func (out *alsaOutput) isFormatSupported(hwparams *C.snd_pcm_hw_params_t, format C.snd_pcm_format_t) bool {
105+
errCode := C.snd_pcm_hw_params_test_format(out.pcmHandle, hwparams, format)
106+
if errCode < 0 {
107+
return false
108+
}
109+
110+
return true
111+
}
112+
103113
func (out *alsaOutput) setupPcm() error {
104114
cdevice := C.CString(out.device)
105115
defer C.free(unsafe.Pointer(cdevice))
@@ -119,8 +129,24 @@ func (out *alsaOutput) setupPcm() error {
119129
return out.alsaError("snd_pcm_hw_params_set_access", err)
120130
}
121131

122-
if err := C.snd_pcm_hw_params_set_format(out.pcmHandle, hwparams, C.SND_PCM_FORMAT_FLOAT_LE); err < 0 {
123-
return out.alsaError("snd_pcm_hw_params_set_format", err)
132+
var formatFound bool
133+
for _, format := range []C.snd_pcm_format_t{
134+
C.SND_PCM_FORMAT_FLOAT_LE, // 32-bit floating point, little-endian
135+
C.SND_PCM_FORMAT_S32_LE, // 32-bit signed integer, little-endian
136+
C.SND_PCM_FORMAT_S16_LE, // 16-bit signed integer, little-endian
137+
} {
138+
if !out.isFormatSupported(hwparams, format) {
139+
continue
140+
}
141+
142+
if err := C.snd_pcm_hw_params_set_format(out.pcmHandle, hwparams, format); err < 0 {
143+
return out.alsaError("snd_pcm_hw_params_set_format", err)
144+
}
145+
formatFound = true
146+
}
147+
148+
if !formatFound {
149+
return fmt.Errorf("could not find a supported PCM format")
124150
}
125151

126152
if err := C.snd_pcm_hw_params_set_channels(out.pcmHandle, hwparams, C.unsigned(out.channels)); err < 0 {
@@ -173,6 +199,13 @@ func (out *alsaOutput) setupPcm() error {
173199
out.bufferSize = int(frames)
174200
}
175201

202+
var pcmFormat C.snd_pcm_format_t
203+
if err := C.snd_pcm_hw_params_get_format(hwparams, &pcmFormat); err < 0 {
204+
return out.alsaError("snd_pcm_hw_params_get_format", err)
205+
} else {
206+
out.pcmFormat = pcmFormat
207+
}
208+
176209
var swparams *C.snd_pcm_sw_params_t
177210
C.snd_pcm_sw_params_malloc(&swparams)
178211
defer C.free(unsafe.Pointer(swparams))
@@ -202,6 +235,45 @@ func (out *alsaOutput) setupPcm() error {
202235
return nil
203236
}
204237

238+
func pcmFormatName(format C.snd_pcm_format_t) string {
239+
switch format {
240+
case C.SND_PCM_FORMAT_S8:
241+
return "S8"
242+
case C.SND_PCM_FORMAT_U8:
243+
return "U8"
244+
case C.SND_PCM_FORMAT_S16_LE:
245+
return "S16_LE"
246+
case C.SND_PCM_FORMAT_S16_BE:
247+
return "S16_BE"
248+
case C.SND_PCM_FORMAT_U16_LE:
249+
return "U16_LE"
250+
case C.SND_PCM_FORMAT_U16_BE:
251+
return "U16_BE"
252+
case C.SND_PCM_FORMAT_S24_LE:
253+
return "S24_LE"
254+
case C.SND_PCM_FORMAT_S24_BE:
255+
return "S24_BE"
256+
case C.SND_PCM_FORMAT_U24_LE:
257+
return "U24_LE"
258+
case C.SND_PCM_FORMAT_U24_BE:
259+
return "U24_BE"
260+
case C.SND_PCM_FORMAT_S32_LE:
261+
return "S32_LE"
262+
case C.SND_PCM_FORMAT_S32_BE:
263+
return "S32_BE"
264+
case C.SND_PCM_FORMAT_U32_LE:
265+
return "U32_LE"
266+
case C.SND_PCM_FORMAT_U32_BE:
267+
return "U32_BE"
268+
case C.SND_PCM_FORMAT_FLOAT_LE:
269+
return "FLOAT_LE"
270+
case C.SND_PCM_FORMAT_FLOAT_BE:
271+
return "FLOAT_BE"
272+
default:
273+
return "unknown"
274+
}
275+
}
276+
205277
func (out *alsaOutput) logParams(params *C.snd_pcm_hw_params_t) error {
206278
var dir C.int
207279

@@ -235,12 +307,51 @@ func (out *alsaOutput) logParams(params *C.snd_pcm_hw_params_t) error {
235307
return out.alsaError("snd_pcm_hw_params_get_periods", err)
236308
}
237309

238-
out.log.Debugf("alsa driver configured, rate = %d bps, period time = %d us, period size = %d frames, buffer time = %d us, buffer size = %d frames, periods per buffer = %d frames",
239-
rate, periodTime, frames, bufferTime, bufferSize, periods)
310+
var format C.snd_pcm_format_t
311+
if err := C.snd_pcm_hw_params_get_format(params, &format); err < 0 {
312+
return out.alsaError("snd_pcm_hw_params_get_format", err)
313+
}
314+
315+
out.log.Debugf("alsa driver configured, rate = %d bps, period time = %d us, period size = %d frames, buffer time = %d us, buffer size = %d frames, periods per buffer = %d frames, PCM format = %s",
316+
rate, periodTime, frames, bufferTime, bufferSize, periods, pcmFormatName(format))
240317

241318
return nil
242319
}
243320

321+
func floatLeToS16Le(floats []float32) []C.int16_t {
322+
shorts := make([]C.int16_t, len(floats))
323+
for i, f := range floats {
324+
// Clip the float to the range [-1.0, 1.0]
325+
if f < -1.0 {
326+
f = -1.0
327+
}
328+
if f > 1.0 {
329+
f = 1.0
330+
}
331+
332+
// Convert to int16 (short) in the range [-32768, 32767]
333+
shorts[i] = C.int16_t(f * 32767)
334+
}
335+
return shorts
336+
}
337+
338+
func floatLeToS32Le(floats []float32) []C.int32_t {
339+
ints := make([]C.int32_t, len(floats))
340+
for i, f := range floats {
341+
// Clip the float to the range [-1.0, 1.0]
342+
if f < -1.0 {
343+
f = -1.0
344+
}
345+
if f > 1.0 {
346+
f = 1.0
347+
}
348+
349+
// Convert to int32 in the range [-2147483648, 2147483647]
350+
ints[i] = C.int32_t(f * 2147483647)
351+
}
352+
return ints
353+
}
354+
244355
func (out *alsaOutput) outputLoop(pcmHandle *C.snd_pcm_t) {
245356
floats := make([]float32, out.channels*out.periodSize)
246357

@@ -296,7 +407,24 @@ func (out *alsaOutput) outputLoop(pcmHandle *C.snd_pcm_t) {
296407
// be very fast. It might be delayed a bit however if the sleep above
297408
// didn't sleep long enough to wait for room in the buffer.
298409
if n > 0 {
299-
if nn := C.snd_pcm_writei(pcmHandle, unsafe.Pointer(&floats[0]), C.snd_pcm_uframes_t(n/out.channels)); nn < 0 {
410+
var data unsafe.Pointer
411+
switch out.pcmFormat {
412+
case C.SND_PCM_FORMAT_FLOAT_LE:
413+
data = unsafe.Pointer(&floats[0])
414+
case C.SND_PCM_FORMAT_S32_LE:
415+
ints := floatLeToS32Le(floats)
416+
data = unsafe.Pointer(&ints[0])
417+
case C.SND_PCM_FORMAT_S16_LE:
418+
shorts := floatLeToS16Le(floats)
419+
data = unsafe.Pointer(&shorts[0])
420+
default:
421+
out.err <- fmt.Errorf("unsupported PCM format: %s", pcmFormatName(out.pcmFormat))
422+
out.closed = true
423+
out.lock.Unlock()
424+
return
425+
}
426+
427+
if nn := C.snd_pcm_writei(pcmHandle, data, C.snd_pcm_uframes_t(n/out.channels)); nn < 0 {
300428
// Got an error, so must recover (even for an underrun).
301429
errCode := C.snd_pcm_recover(pcmHandle, C.int(nn), 1)
302430
if errCode < 0 {

0 commit comments

Comments
 (0)