Commit | Line | Data |
---|---|---|
ef79bbde P |
1 | /*************************************************************************** |
2 | pulseaudio.c - description | |
3 | ------------------- | |
4 | begin : Thu Feb 04 2010 | |
5 | copyright : (C) 2010 by Tristin Celestin | |
6 | email : cetris1@umbc.edu | |
7 | comment : Much of this was taken from simple.c, in the pulseaudio | |
8 | library | |
9 | ***************************************************************************/ | |
10 | /*************************************************************************** | |
11 | * * | |
12 | * This program is free software; you can redistribute it and/or modify * | |
13 | * it under the terms of the GNU General Public License as published by * | |
14 | * the Free Software Foundation; either version 2 of the License, or * | |
15 | * (at your option) any later version. See also the license.txt file for * | |
16 | * additional informations. * | |
17 | * * | |
18 | ***************************************************************************/ | |
19 | ||
b68bf5c2 | 20 | #include <stdio.h> |
ef79bbde | 21 | |
ef79bbde | 22 | #include <pulse/pulseaudio.h> |
b68bf5c2 | 23 | #include "out.h" |
ef79bbde P |
24 | |
25 | //////////////////////////////////////////////////////////////////////// | |
26 | // pulseaudio structs | |
27 | //////////////////////////////////////////////////////////////////////// | |
28 | ||
29 | typedef struct { | |
30 | pa_threaded_mainloop *mainloop; | |
31 | pa_context *context; | |
32 | pa_mainloop_api *api; | |
33 | pa_stream *stream; | |
34 | pa_sample_spec spec; | |
35 | int first; | |
36 | } Device; | |
37 | ||
38 | typedef struct { | |
39 | unsigned int frequency; | |
40 | unsigned int latency_in_msec; | |
41 | } Settings; | |
42 | ||
43 | //////////////////////////////////////////////////////////////////////// | |
44 | // pulseaudio globals | |
45 | //////////////////////////////////////////////////////////////////////// | |
46 | ||
47 | static Device device = { | |
48 | .mainloop = NULL, | |
49 | .api = NULL, | |
50 | .context = NULL, | |
51 | .stream = NULL | |
52 | }; | |
53 | ||
54 | static Settings settings = { | |
55 | .frequency = 44100, | |
56 | .latency_in_msec = 20, | |
57 | }; | |
58 | ||
59 | // the number of bytes written in SoundFeedStreamData | |
60 | const int mixlen = 3240; | |
61 | ||
62 | // used to calculate how much space is used in the buffer, for debugging purposes | |
63 | //int maxlength = 0; | |
64 | ||
65 | //////////////////////////////////////////////////////////////////////// | |
66 | // CALLBACKS FOR THREADED MAINLOOP | |
67 | //////////////////////////////////////////////////////////////////////// | |
68 | static void context_state_cb (pa_context *context, void *userdata) | |
69 | { | |
70 | Device *dev = userdata; | |
71 | ||
72 | if ((context == NULL) || (dev == NULL)) | |
73 | return; | |
74 | ||
75 | switch (pa_context_get_state (context)) | |
76 | { | |
77 | case PA_CONTEXT_READY: | |
78 | case PA_CONTEXT_TERMINATED: | |
79 | case PA_CONTEXT_FAILED: | |
80 | pa_threaded_mainloop_signal (dev->mainloop, 0); | |
81 | break; | |
82 | ||
83 | case PA_CONTEXT_UNCONNECTED: | |
84 | case PA_CONTEXT_CONNECTING: | |
85 | case PA_CONTEXT_AUTHORIZING: | |
86 | case PA_CONTEXT_SETTING_NAME: | |
87 | break; | |
88 | } | |
89 | } | |
90 | ||
91 | static void stream_state_cb (pa_stream *stream, void * userdata) | |
92 | { | |
93 | Device *dev = userdata; | |
94 | ||
95 | if ((stream == NULL) || (dev == NULL)) | |
96 | return; | |
97 | ||
98 | switch (pa_stream_get_state (stream)) | |
99 | { | |
100 | case PA_STREAM_READY: | |
101 | case PA_STREAM_FAILED: | |
102 | case PA_STREAM_TERMINATED: | |
103 | pa_threaded_mainloop_signal (dev->mainloop, 0); | |
104 | break; | |
105 | ||
106 | case PA_STREAM_UNCONNECTED: | |
107 | case PA_STREAM_CREATING: | |
108 | break; | |
109 | } | |
110 | } | |
111 | ||
112 | static void stream_latency_update_cb (pa_stream *stream, void *userdata) | |
113 | { | |
114 | Device *dev = userdata; | |
115 | ||
116 | if ((stream == NULL) || (dev == NULL)) | |
117 | return; | |
118 | ||
119 | pa_threaded_mainloop_signal (dev->mainloop, 0); | |
120 | } | |
121 | ||
122 | static void stream_request_cb (pa_stream *stream, size_t length, void *userdata) | |
123 | { | |
124 | Device *dev = userdata; | |
125 | ||
126 | if ((stream == NULL) || (dev == NULL)) | |
127 | return; | |
128 | pa_threaded_mainloop_signal (dev->mainloop, 0); | |
129 | } | |
130 | ||
131 | //////////////////////////////////////////////////////////////////////// | |
132 | // SETUP SOUND | |
133 | //////////////////////////////////////////////////////////////////////// | |
134 | ||
b68bf5c2 | 135 | static int pulse_init(void) |
ef79bbde P |
136 | { |
137 | int error_number; | |
138 | ||
139 | // Acquire mainloop /////////////////////////////////////////////////////// | |
140 | device.mainloop = pa_threaded_mainloop_new (); | |
141 | if (device.mainloop == NULL) | |
142 | { | |
143 | fprintf (stderr, "Could not acquire PulseAudio main loop\n"); | |
07c13dfd | 144 | return -1; |
ef79bbde P |
145 | } |
146 | ||
147 | // Acquire context //////////////////////////////////////////////////////// | |
148 | device.api = pa_threaded_mainloop_get_api (device.mainloop); | |
149 | device.context = pa_context_new (device.api, "PCSX"); | |
150 | pa_context_set_state_callback (device.context, context_state_cb, &device); | |
151 | ||
152 | if (device.context == NULL) | |
153 | { | |
154 | fprintf (stderr, "Could not acquire PulseAudio device context\n"); | |
07c13dfd | 155 | return -1; |
ef79bbde P |
156 | } |
157 | ||
158 | // Connect to PulseAudio server /////////////////////////////////////////// | |
159 | if (pa_context_connect (device.context, NULL, 0, NULL) < 0) | |
160 | { | |
161 | error_number = pa_context_errno (device.context); | |
162 | fprintf (stderr, "Could not connect to PulseAudio server: %s\n", pa_strerror(error_number)); | |
07c13dfd | 163 | return -1; |
ef79bbde P |
164 | } |
165 | ||
166 | // Run mainloop until sever context is ready ////////////////////////////// | |
167 | pa_threaded_mainloop_lock (device.mainloop); | |
168 | if (pa_threaded_mainloop_start (device.mainloop) < 0) | |
169 | { | |
170 | fprintf (stderr, "Could not start mainloop\n"); | |
07c13dfd | 171 | return -1; |
ef79bbde P |
172 | } |
173 | ||
174 | pa_context_state_t context_state; | |
175 | context_state = pa_context_get_state (device.context); | |
176 | while (context_state != PA_CONTEXT_READY) | |
177 | { | |
178 | context_state = pa_context_get_state (device.context); | |
179 | if (! PA_CONTEXT_IS_GOOD (context_state)) | |
180 | { | |
181 | error_number = pa_context_errno (device.context); | |
182 | fprintf (stderr, "Context state is not good: %s\n", pa_strerror (error_number)); | |
07c13dfd | 183 | return -1; |
ef79bbde P |
184 | } |
185 | else if (context_state == PA_CONTEXT_READY) | |
186 | break; | |
187 | else | |
188 | fprintf (stderr, "PulseAudio context state is %d\n", context_state); | |
189 | pa_threaded_mainloop_wait (device.mainloop); | |
190 | } | |
191 | ||
192 | // Set sample spec //////////////////////////////////////////////////////// | |
193 | device.spec.format = PA_SAMPLE_S16NE; | |
97ea4077 | 194 | device.spec.channels = 2; |
ef79bbde P |
195 | device.spec.rate = settings.frequency; |
196 | ||
197 | pa_buffer_attr buffer_attributes; | |
198 | buffer_attributes.tlength = pa_bytes_per_second (& device.spec) / 5; | |
199 | buffer_attributes.maxlength = buffer_attributes.tlength * 3; | |
200 | buffer_attributes.minreq = buffer_attributes.tlength / 3; | |
201 | buffer_attributes.prebuf = buffer_attributes.tlength; | |
202 | ||
203 | //maxlength = buffer_attributes.maxlength; | |
204 | //fprintf (stderr, "Total space: %u\n", buffer_attributes.maxlength); | |
205 | //fprintf (stderr, "Minimum request size: %u\n", buffer_attributes.minreq); | |
206 | //fprintf (stderr, "Bytes needed before playback: %u\n", buffer_attributes.prebuf); | |
207 | //fprintf (stderr, "Target buffer size: %lu\n", buffer_attributes.tlength); | |
208 | ||
209 | // Acquire new stream using spec ////////////////////////////////////////// | |
210 | device.stream = pa_stream_new (device.context, "PCSX", &device.spec, NULL); | |
211 | if (device.stream == NULL) | |
212 | { | |
213 | error_number = pa_context_errno (device.context); | |
214 | fprintf (stderr, "Could not acquire new PulseAudio stream: %s\n", pa_strerror (error_number)); | |
07c13dfd | 215 | return -1; |
ef79bbde P |
216 | } |
217 | ||
218 | // Set callbacks for server events //////////////////////////////////////// | |
219 | pa_stream_set_state_callback (device.stream, stream_state_cb, &device); | |
220 | pa_stream_set_write_callback (device.stream, stream_request_cb, &device); | |
221 | pa_stream_set_latency_update_callback (device.stream, stream_latency_update_cb, &device); | |
222 | ||
223 | // Ready stream for playback ////////////////////////////////////////////// | |
224 | pa_stream_flags_t flags = (pa_stream_flags_t) (PA_STREAM_ADJUST_LATENCY | PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE); | |
225 | //pa_stream_flags_t flags = (pa_stream_flags_t) (PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_EARLY_REQUESTS); | |
226 | if (pa_stream_connect_playback (device.stream, NULL, &buffer_attributes, flags, NULL, NULL) < 0) | |
227 | { | |
b68bf5c2 | 228 | error_number = pa_context_errno (device.context); |
ef79bbde | 229 | fprintf (stderr, "Could not connect for playback: %s\n", pa_strerror (error_number)); |
07c13dfd | 230 | return -1; |
ef79bbde P |
231 | } |
232 | ||
233 | // Run mainloop until stream is ready ///////////////////////////////////// | |
234 | pa_stream_state_t stream_state; | |
235 | stream_state = pa_stream_get_state (device.stream); | |
236 | while (stream_state != PA_STREAM_READY) | |
237 | { | |
238 | stream_state = pa_stream_get_state (device.stream); | |
239 | ||
240 | if (stream_state == PA_STREAM_READY) | |
241 | break; | |
242 | ||
243 | else if (! PA_STREAM_IS_GOOD (stream_state)) | |
244 | { | |
245 | error_number = pa_context_errno (device.context); | |
246 | fprintf (stderr, "Stream state is not good: %s\n", pa_strerror (error_number)); | |
07c13dfd | 247 | return -1; |
ef79bbde P |
248 | } |
249 | else | |
250 | fprintf (stderr, "PulseAudio stream state is %d\n", stream_state); | |
251 | pa_threaded_mainloop_wait (device.mainloop); | |
252 | } | |
253 | ||
254 | pa_threaded_mainloop_unlock (device.mainloop); | |
255 | ||
256 | fprintf (stderr, "PulseAudio should be connected\n"); | |
07c13dfd | 257 | return 0; |
ef79bbde P |
258 | } |
259 | ||
260 | //////////////////////////////////////////////////////////////////////// | |
261 | // REMOVE SOUND | |
262 | //////////////////////////////////////////////////////////////////////// | |
07c13dfd | 263 | static void pulse_finish(void) |
ef79bbde P |
264 | { |
265 | if (device.mainloop != NULL) | |
266 | pa_threaded_mainloop_stop (device.mainloop); | |
267 | ||
268 | // Release in reverse order of acquisition | |
269 | if (device.stream != NULL) | |
270 | { | |
271 | pa_stream_unref (device.stream); | |
272 | device.stream = NULL; | |
273 | ||
274 | } | |
275 | if (device.context != NULL) | |
276 | { | |
277 | pa_context_disconnect (device.context); | |
278 | pa_context_unref (device.context); | |
279 | device.context = NULL; | |
280 | } | |
281 | ||
282 | if (device.mainloop != NULL) | |
283 | { | |
284 | pa_threaded_mainloop_free (device.mainloop); | |
285 | device.mainloop = NULL; | |
286 | } | |
287 | ||
288 | } | |
289 | ||
290 | //////////////////////////////////////////////////////////////////////// | |
291 | // GET BYTES BUFFERED | |
292 | //////////////////////////////////////////////////////////////////////// | |
293 | ||
07c13dfd | 294 | static int pulse_busy(void) |
ef79bbde P |
295 | { |
296 | int free_space; | |
ef79bbde P |
297 | |
298 | if ((device.mainloop == NULL) || (device.api == NULL) || ( device.context == NULL) || (device.stream == NULL)) | |
f8edb5bc | 299 | return 1; |
ef79bbde P |
300 | |
301 | pa_threaded_mainloop_lock (device.mainloop); | |
302 | free_space = pa_stream_writable_size (device.stream); | |
303 | pa_threaded_mainloop_unlock (device.mainloop); | |
304 | ||
305 | //fprintf (stderr, "Free space: %d\n", free_space); | |
306 | //fprintf (stderr, "Used space: %d\n", maxlength - free_space); | |
307 | if (free_space < mixlen * 3) | |
308 | { | |
309 | // Don't buffer anymore, just play | |
310 | //fprintf (stderr, "Not buffering.\n"); | |
f8edb5bc | 311 | return 1; |
ef79bbde P |
312 | } |
313 | else | |
314 | { | |
315 | // Buffer some sound | |
316 | //fprintf (stderr, "Buffering.\n"); | |
317 | return 0; | |
318 | } | |
319 | } | |
320 | ||
321 | //////////////////////////////////////////////////////////////////////// | |
322 | // FEED SOUND DATA | |
323 | //////////////////////////////////////////////////////////////////////// | |
324 | ||
07c13dfd | 325 | static void pulse_feed(void *pSound, int lBytes) |
ef79bbde | 326 | { |
ef79bbde P |
327 | if (device.mainloop != NULL) |
328 | { | |
329 | pa_threaded_mainloop_lock (device.mainloop); | |
330 | if (pa_stream_write (device.stream, pSound, lBytes, NULL, 0LL, PA_SEEK_RELATIVE) < 0) | |
331 | { | |
332 | fprintf (stderr, "Could not perform write\n"); | |
333 | } | |
334 | else | |
335 | { | |
336 | //fprintf (stderr, "Wrote %d bytes\n", lBytes); | |
337 | pa_threaded_mainloop_unlock (device.mainloop); | |
338 | } | |
339 | } | |
340 | } | |
07c13dfd | 341 | |
342 | void out_register_pulse(struct out_driver *drv) | |
343 | { | |
344 | drv->name = "pulseaudio"; | |
345 | drv->init = pulse_init; | |
346 | drv->finish = pulse_finish; | |
347 | drv->busy = pulse_busy; | |
348 | drv->feed = pulse_feed; | |
349 | } |