@@ -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+
103144func (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+
205288func (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+
244349func (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