Skip to content

Commit b97d500

Browse files
committed
feat: support SND_PCM_FORMAT_S16_LE ALSA format
Thanks to @pilollipietro, see #160
1 parent b671638 commit b97d500

File tree

1 file changed

+144
-25
lines changed

1 file changed

+144
-25
lines changed

output/driver-alsa.go

Lines changed: 144 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,12 @@ type alsaOutput struct {
3333

3434
lock sync.Mutex
3535

36-
pcmHandle *C.snd_pcm_t // nil when pcmHandle is closed
37-
bufferTime int
38-
periodCount int
39-
periodSize int
40-
bufferSize int
36+
pcmHandle *C.snd_pcm_t // nil when pcmHandle is closed
37+
bufferTime int
38+
periodCount int
39+
periodSize int
40+
bufferSize int
41+
deviceFormat int
4142

4243
externalVolume bool
4344

@@ -100,6 +101,46 @@ 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 isDeviceFormatSupported(pcmHandle *C.snd_pcm_t, hwparams *C.snd_pcm_hw_params_t, format C.snd_pcm_format_t) bool {
105+
errCode := C.snd_pcm_hw_params_test_format(pcmHandle, hwparams, format)
106+
if errCode < 0 {
107+
return false
108+
}
109+
110+
return true
111+
}
112+
113+
func (out *alsaOutput) configureAdaptiveBuffer(hwparams *C.snd_pcm_hw_params_t) error {
114+
var bufferSize C.snd_pcm_uframes_t
115+
if err := C.snd_pcm_hw_params_get_buffer_size(hwparams, &bufferSize); err == 0 {
116+
// Do the rest only if snd_pcm_hw_params_get_buffer_size returns successfully.
117+
// If it fails, snd_pcm_hw_params_get_buffer_size will fail and
118+
// snd_pcm_hw_params_set_buffer_time_near will not work either.
119+
// In that case, we will use min/max values or adaptive buffers.
120+
bufferTime := C.uint(out.bufferTime)
121+
if err := C.snd_pcm_hw_params_set_buffer_time_near(out.pcmHandle, hwparams, &bufferTime, nil); err < 0 {
122+
return out.alsaError("snd_pcm_hw_params_set_buffer_time_near", err)
123+
}
124+
125+
// Request a period size that's approximately bufferSize/4.
126+
// By default, ALSA might use a very short buffer size (e.g., 220) that could
127+
// lead to crackling sounds. So we request a buffer with a reasonable period size.
128+
var periodSize C.snd_pcm_uframes_t = C.snd_pcm_uframes_t(bufferSize) / C.snd_pcm_uframes_t(out.periodCount)
129+
if err := C.snd_pcm_hw_params_set_period_size_near(out.pcmHandle, hwparams, &periodSize, nil); err < 0 {
130+
return out.alsaError("snd_pcm_hw_params_set_period_size_near", err)
131+
}
132+
133+
return nil
134+
} else {
135+
// May be that the configuration space does not contain a single value for buffer size.
136+
// In this case snd_pcm_hw_params_get_buffer_size_min and snd_pcm_hw_params_get_buffer_size_max are available.
137+
// snd_pcm_hw_params_set_buffer_time_near will fail but the buffer can be set with snd_pcm_hw_params_set_buffer_time_minmax.
138+
// snd_pcm_hw_params_set_period_size_near might fail but snd_pcm_hw_params_set_period_size_minmax is available.
139+
// It may be that in this case nothing needs to be done as the buffers should be adaptive.
140+
return out.alsaError("snd_pcm_hw_params_get_buffer_size", err)
141+
}
142+
}
143+
103144
func (out *alsaOutput) setupPcm() error {
104145
cdevice := C.CString(out.device)
105146
defer C.free(unsafe.Pointer(cdevice))
@@ -119,8 +160,17 @@ func (out *alsaOutput) setupPcm() error {
119160
return out.alsaError("snd_pcm_hw_params_set_access", err)
120161
}
121162

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)
163+
formats := []C.snd_pcm_format_t{
164+
C.SND_PCM_FORMAT_FLOAT_LE, // 32-bit floating point, little-endian
165+
C.SND_PCM_FORMAT_S16_LE, // 16-bit signed integer, little-endian
166+
}
167+
168+
for _, format := range formats {
169+
if isDeviceFormatSupported(out.pcmHandle, hwparams, format) {
170+
if err := C.snd_pcm_hw_params_set_format(out.pcmHandle, hwparams, format); err < 0 {
171+
return out.alsaError("snd_pcm_hw_params_set_format", err)
172+
}
173+
}
124174
}
125175

126176
if err := C.snd_pcm_hw_params_set_channels(out.pcmHandle, hwparams, C.unsigned(out.channels)); err < 0 {
@@ -136,21 +186,8 @@ func (out *alsaOutput) setupPcm() error {
136186
return out.alsaError("snd_pcm_hw_params_set_rate_near", err)
137187
}
138188

139-
bufferTime := C.uint(out.bufferTime)
140-
if err := C.snd_pcm_hw_params_set_buffer_time_near(out.pcmHandle, hwparams, &bufferTime, nil); err < 0 {
141-
return out.alsaError("snd_pcm_hw_params_set_buffer_time_near", err)
142-
}
143-
144-
// Request a period size that's approximately bufferSize/4.
145-
// By default, it might use a really short buffer size like 220 which can
146-
// lead to crackling.
147-
var bufferSize C.snd_pcm_uframes_t
148-
if err := C.snd_pcm_hw_params_get_buffer_size(hwparams, &bufferSize); err < 0 {
149-
return out.alsaError("snd_pcm_hw_params_get_buffer_size", err)
150-
}
151-
var periodSize C.snd_pcm_uframes_t = C.snd_pcm_uframes_t(bufferSize) / C.snd_pcm_uframes_t(out.periodCount)
152-
if err := C.snd_pcm_hw_params_set_period_size_near(out.pcmHandle, hwparams, &periodSize, nil); err < 0 {
153-
return out.alsaError("snd_pcm_hw_params_set_period_size_near", err)
189+
if err := out.configureAdaptiveBuffer(hwparams); err != nil {
190+
return err
154191
}
155192

156193
if err := C.snd_pcm_hw_params(out.pcmHandle, hwparams); err < 0 {
@@ -173,6 +210,13 @@ func (out *alsaOutput) setupPcm() error {
173210
out.bufferSize = int(frames)
174211
}
175212

213+
var currentFormat C.snd_pcm_format_t
214+
if err := C.snd_pcm_hw_params_get_format(hwparams, &currentFormat); err < 0 {
215+
return out.alsaError("snd_pcm_hw_params_get_format", err)
216+
} else {
217+
out.deviceFormat = int(currentFormat)
218+
}
219+
176220
var swparams *C.snd_pcm_sw_params_t
177221
C.snd_pcm_sw_params_malloc(&swparams)
178222
defer C.free(unsafe.Pointer(swparams))
@@ -202,6 +246,45 @@ func (out *alsaOutput) setupPcm() error {
202246
return nil
203247
}
204248

249+
func deviceFormatName(format int) string {
250+
switch format {
251+
case int(C.SND_PCM_FORMAT_S8):
252+
return "S8"
253+
case int(C.SND_PCM_FORMAT_U8):
254+
return "U8"
255+
case int(C.SND_PCM_FORMAT_S16_LE):
256+
return "S16_LE"
257+
case int(C.SND_PCM_FORMAT_S16_BE):
258+
return "S16_BE"
259+
case int(C.SND_PCM_FORMAT_U16_LE):
260+
return "U16_LE"
261+
case int(C.SND_PCM_FORMAT_U16_BE):
262+
return "U16_BE"
263+
case int(C.SND_PCM_FORMAT_S24_LE):
264+
return "S24_LE"
265+
case int(C.SND_PCM_FORMAT_S24_BE):
266+
return "S24_BE"
267+
case int(C.SND_PCM_FORMAT_U24_LE):
268+
return "U24_LE"
269+
case int(C.SND_PCM_FORMAT_U24_BE):
270+
return "U24_BE"
271+
case int(C.SND_PCM_FORMAT_S32_LE):
272+
return "S32_LE"
273+
case int(C.SND_PCM_FORMAT_S32_BE):
274+
return "S32_BE"
275+
case int(C.SND_PCM_FORMAT_U32_LE):
276+
return "U32_LE"
277+
case int(C.SND_PCM_FORMAT_U32_BE):
278+
return "U32_BE"
279+
case int(C.SND_PCM_FORMAT_FLOAT_LE):
280+
return "FLOAT_LE"
281+
case int(C.SND_PCM_FORMAT_FLOAT_BE):
282+
return "FLOAT_BE"
283+
default:
284+
return "unknown"
285+
}
286+
}
287+
205288
func (out *alsaOutput) logParams(params *C.snd_pcm_hw_params_t) error {
206289
var dir C.int
207290

@@ -235,12 +318,34 @@ func (out *alsaOutput) logParams(params *C.snd_pcm_hw_params_t) error {
235318
return out.alsaError("snd_pcm_hw_params_get_periods", err)
236319
}
237320

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)
321+
var currentFormat C.snd_pcm_format_t
322+
if err := C.snd_pcm_hw_params_get_format(params, &currentFormat); err < 0 {
323+
return out.alsaError("snd_pcm_hw_params_get_format", err)
324+
}
325+
326+
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, device format = %s",
327+
rate, periodTime, frames, bufferTime, bufferSize, periods, deviceFormatName(int(currentFormat)))
240328

241329
return nil
242330
}
243331

332+
func floatLeToS16Le(floats []float32) []C.int16_t {
333+
shorts := make([]C.int16_t, len(floats))
334+
for i, f := range floats {
335+
// Clip the float to the range [-1.0, 1.0]
336+
if f < -1.0 {
337+
f = -1.0
338+
}
339+
if f > 1.0 {
340+
f = 1.0
341+
}
342+
343+
// Convert to int16 (short) in the range [-32768, 32767]
344+
shorts[i] = C.int16_t(f * 32767)
345+
}
346+
return shorts
347+
}
348+
244349
func (out *alsaOutput) outputLoop(pcmHandle *C.snd_pcm_t) {
245350
floats := make([]float32, out.channels*out.periodSize)
246351

@@ -296,7 +401,21 @@ func (out *alsaOutput) outputLoop(pcmHandle *C.snd_pcm_t) {
296401
// be very fast. It might be delayed a bit however if the sleep above
297402
// didn't sleep long enough to wait for room in the buffer.
298403
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 {
404+
var data unsafe.Pointer
405+
switch out.deviceFormat {
406+
case C.SND_PCM_FORMAT_FLOAT_LE:
407+
data = unsafe.Pointer(&floats[0])
408+
case C.SND_PCM_FORMAT_S16_LE:
409+
shorts := floatLeToS16Le(floats)
410+
data = unsafe.Pointer(&shorts[0])
411+
default:
412+
out.err <- fmt.Errorf("unsupported device format: %s", deviceFormatName(out.deviceFormat))
413+
out.closed = true
414+
out.lock.Unlock()
415+
return
416+
}
417+
418+
if nn := C.snd_pcm_writei(pcmHandle, data, C.snd_pcm_uframes_t(n/out.channels)); nn < 0 {
300419
// Got an error, so must recover (even for an underrun).
301420
errCode := C.snd_pcm_recover(pcmHandle, C.int(nn), 1)
302421
if errCode < 0 {

0 commit comments

Comments
 (0)