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