1 /* pulsesound.c: pulseaudio (Linux) sound I/O
2    Copyright (c) 2010-2019 Grzegorz Jablonski, Sergio Baldoví
3 
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 2 of the License, or
7    (at your option) any later version.
8 
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13 
14    You should have received a copy of the GNU General Public License along
15    with this program; if not, write to the Free Software Foundation, Inc.,
16    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 
18 */
19 
20 #include <config.h>
21 
22 #include <stdio.h>
23 #include <string.h>
24 
25 #include <libspectrum.h>
26 #include <pulse/error.h>
27 #include <pulse/simple.h>
28 #include <pulse/timeval.h>
29 
30 #include "sound.h"
31 
32 #define PULSEAUDIO_DEBUG 0
33 
34 static pa_simple *pulse_s;
35 static int verbose = 0;
36 
37 void
sound_lowlevel_end(void)38 sound_lowlevel_end( void )
39 {
40   pa_simple_free( pulse_s );
41 }
42 
43 int
sound_lowlevel_init(const char * device,int * freqptr,int * stereoptr)44 sound_lowlevel_init( const char *device, int *freqptr, int *stereoptr )
45 {
46   pa_sample_spec ss;
47   pa_buffer_attr buf;
48   unsigned int n;
49   unsigned int val;
50   const char *option;
51   char tmp;
52   int err;
53   int bsize = 0;
54   int bdelay = 30; /* default to 30ms */
55 
56   /* scan explicitly given parameters */
57   option = device;
58   while( option && *option ) {
59     tmp = '*';
60     if( ( err = sscanf( option, " tlength=%ims %n%c", &val, &n, &tmp ) > 0 ) &&
61         ( tmp == ',' || strlen( option ) == n ) ) {
62       if( val < 1 ) {
63         fprintf( stderr, "Bad value for PULSEAUDIO tlength, using default\n" );
64       } else {
65         bdelay = val;
66       }
67     } else if( ( err = sscanf( option, " tlength=%i %n%c", &val, &n, &tmp ) > 0 ) &&
68         ( tmp == ',' || strlen( option ) == n ) ) {
69       if( val < 1 ) {
70         fprintf( stderr, "Bad value for PULSEAUDIO tlength, using default\n" );
71       } else {
72         bsize = val;
73       }
74     } else if( ( err = sscanf( option, " verbose %n%c", &n, &tmp ) == 1 ) &&
75               ( tmp == ',' || ( strlen( option ) == n ) ) ) {
76       verbose = 1;
77     }
78 
79     option += n + ( tmp == ',' );
80   }
81 
82 #if defined WORDS_BIGENDIAN
83   ss.format = PA_SAMPLE_S16BE;
84 #else
85   ss.format = PA_SAMPLE_S16LE;
86 #endif
87 
88   ss.channels = ( *stereoptr )? 2 : 1;
89   ss.rate = *freqptr;
90 
91   /* Reduce latency to 30ms or the user-defined value. pulseaudio does
92      not guarantee that */
93   buf.tlength = (bsize)? bsize :
94                          pa_usec_to_bytes( bdelay * PA_USEC_PER_MSEC, &ss );
95 
96   buf.maxlength = buf.tlength * 4;
97   buf.minreq = buf.tlength / 4;
98   buf.prebuf = (uint32_t) -1;
99   buf.fragsize = (uint32_t) -1;
100 
101   if( verbose ) {
102     fprintf( stdout, "buffer requested: maxlength=%lu, tlength=%lu, prebuf=%lu,"
103              " minreq=%lu\n",
104              (unsigned long) buf.maxlength,
105              (unsigned long) buf.tlength,
106              (unsigned long) buf.prebuf,
107              (unsigned long) buf.minreq );
108   }
109 
110   pulse_s = pa_simple_new( NULL, PACKAGE, PA_STREAM_PLAYBACK, NULL,
111                            "Spectrum", &ss, NULL, &buf, &err );
112   if( pulse_s == NULL ) {
113     fprintf( stderr, "pulseaudio: pa_simple_new() failed: %s\n",
114              pa_strerror( err ) );
115     return 1;
116   }
117 
118   return 0;
119 }
120 
121 void
sound_lowlevel_frame(libspectrum_signed_word * data,int len)122 sound_lowlevel_frame( libspectrum_signed_word *data, int len )
123 {
124   int retval, error;
125 
126   /* Measure sound lag */
127   if( PULSEAUDIO_DEBUG ) {
128     pa_usec_t latency;
129     latency = pa_simple_get_latency( pulse_s, &error );
130     if( latency == (pa_usec_t) - 1 ) {
131       fprintf( stderr, "pulseaudio: pa_simple_get_latency() failed: %s\n",
132                pa_strerror( error ) );
133     } else {
134       fprintf( stderr, "%0.0f ", (float)latency / PA_USEC_PER_MSEC );
135     }
136   }
137 
138   retval = pa_simple_write( pulse_s, data, 2 * len, &error );
139 
140   if( verbose && retval < 0 ) {
141     fprintf( stderr, "pulseaudio: pa_simple_write() failed: %s\n",
142              pa_strerror( error ) );
143   }
144 }
145