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