1 /*
2 * Copyright (c) 2008 Brad Smith <brad@comstyle.com>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 /* ao plugin for sndio by Brad Smith <brad@comstyle.com>. */
18
19 #ifdef HAVE_CONFIG_H
20 #include "config.h"
21 #endif
22
23 #include <stdio.h>
24 #include <errno.h>
25 #include <string.h>
26 #include <stdlib.h>
27 #include <fcntl.h>
28 #include <math.h>
29 #include <unistd.h>
30 #include <inttypes.h>
31 #include <pthread.h>
32
33 #include <sndio.h>
34
35 #include <xine/xine_internal.h>
36 #include <xine/xineutils.h>
37 #include <xine/audio_out.h>
38 #include "bswap.h"
39
40 #define GAP_TOLERANCE AO_MAX_GAP
41 #define PCT_TO_MIDI(p) (((p) * SIO_MAXVOL + 50) / 100)
42
43 typedef struct {
44 audio_driver_class_t driver_class;
45 xine_t *xine;
46 } sndio_class_t;
47
48 typedef struct sndio_driver_s {
49 ao_driver_t ao_driver;
50 xine_t *xine;
51
52 struct sio_hdl *hdl;
53 long long realpos, playpos;
54 int capabilities;
55
56 int num_channels;
57 u_int32_t bits_per_sample;
58 u_int32_t bytes_per_frame;
59
60 struct {
61 int volume;
62 int mute;
63 } mixer;
64 } sndio_driver_t;
65
66 /*
67 * Callback to notify of frames processed by the hw. It is
68 * called from the mail loop called from sio_write().
69 */
ao_sndio_onmove_cb(void * addr,int delta)70 static void ao_sndio_onmove_cb(void *addr, int delta)
71 {
72 sndio_driver_t *this = (sndio_driver_t *)addr;
73
74 this->realpos += delta;
75 }
76
77 /*
78 * Open the audio device for writing to.
79 */
ao_sndio_open(ao_driver_t * this_gen,uint32_t bits,uint32_t rate,int mode)80 static int ao_sndio_open(ao_driver_t *this_gen,
81 uint32_t bits, uint32_t rate, int mode)
82 {
83 sndio_driver_t *this = (sndio_driver_t *) this_gen;
84 struct sio_par par;
85
86 xprintf (this->xine, XINE_VERBOSITY_DEBUG,
87 "audio_sndio_out: ao_sndio_open bits=%d rate=%d, mode=%d\n",
88 bits, rate, mode);
89
90 if (this->hdl != NULL) {
91 sio_close (this->hdl);
92 this->hdl = NULL;
93 }
94
95 this->hdl = sio_open(NULL, SIO_PLAY, 0);
96 if (this->hdl == NULL)
97 goto bad;
98
99 sio_initpar(&par);
100
101 switch (mode) {
102 case AO_CAP_MODE_MONO:
103 par.pchan = 1;
104 break;
105 case AO_CAP_MODE_STEREO:
106 par.pchan = 2;
107 break;
108 case AO_CAP_MODE_4CHANNEL:
109 par.pchan = 4;
110 break;
111 case AO_CAP_MODE_4_1CHANNEL:
112 case AO_CAP_MODE_5CHANNEL:
113 case AO_CAP_MODE_5_1CHANNEL:
114 par.pchan = 6;
115 break;
116 default:
117 xprintf (this->xine, XINE_VERBOSITY_DEBUG,
118 "audio_sndio_out: ao_sndio_open does not support the requested mode: 0x%X\n",
119 mode);
120 goto bad;
121 }
122
123 switch (bits) {
124 case 8:
125 par.bits = 8;
126 par.sig = 0;
127 break;
128 case 16:
129 par.bits = 16;
130 par.sig = 1;
131 break;
132 default:
133 xprintf (this->xine, XINE_VERBOSITY_DEBUG,
134 "audio_sndio_out: ao_sndio_open bits per sample not supported: %d\n", bits);
135 goto bad;
136 }
137
138 par.rate = rate;
139 par.appbufsz = par.rate * 250 / 1000; /* 250ms buffer */
140
141 if (!sio_setpar(this->hdl, &par)) {
142 xprintf (this->xine, XINE_VERBOSITY_DEBUG,
143 "audio_sndio_out: ao_sndio_open could not set params\n");
144 goto bad;
145 }
146
147 if (!sio_getpar(this->hdl, &par)) {
148 xprintf (this->xine, XINE_VERBOSITY_DEBUG,
149 "audio_sndio_out: ao_sndio_open could not get params\n");
150 goto bad;
151 }
152
153 xprintf (this->xine, XINE_VERBOSITY_DEBUG,
154 "audio_sndio_out: ao_sndio_open %d channels output\n",
155 par.pchan);
156
157 this->num_channels = par.pchan;
158 this->bytes_per_frame = par.bps * par.pchan;
159 this->playpos = 0;
160 this->realpos = 0;
161 sio_onmove(this->hdl, ao_sndio_onmove_cb, this);
162
163 if (!sio_start(this->hdl)) {
164 xprintf (this->xine, XINE_VERBOSITY_DEBUG,
165 "audio_sndio_out: ao_sndio_open could not start\n");
166 goto bad;
167 }
168
169 return par.rate;
170
171 bad:
172 if (this->hdl != NULL)
173 sio_close(this->hdl);
174 return 0;
175 }
176
ao_sndio_num_channels(ao_driver_t * this_gen)177 static int ao_sndio_num_channels(ao_driver_t *this_gen)
178 {
179 sndio_driver_t *this = (sndio_driver_t *) this_gen;
180
181 return this->num_channels;
182 }
183
ao_sndio_bytes_per_frame(ao_driver_t * this_gen)184 static int ao_sndio_bytes_per_frame(ao_driver_t *this_gen)
185 {
186 sndio_driver_t *this = (sndio_driver_t *) this_gen;
187
188 return this->bytes_per_frame;
189 }
190
ao_sndio_get_gap_tolerance(ao_driver_t * this_gen)191 static int ao_sndio_get_gap_tolerance (ao_driver_t *this_gen)
192 {
193 return GAP_TOLERANCE;
194 }
195
ao_sndio_write(ao_driver_t * this_gen,int16_t * data,uint32_t num_frames)196 static int ao_sndio_write(ao_driver_t *this_gen, int16_t *data,
197 uint32_t num_frames)
198 {
199 sndio_driver_t *this = (sndio_driver_t *) this_gen;
200 size_t ret, size = num_frames * this->bytes_per_frame;
201
202 ret = sio_write(this->hdl, data, size);
203 if (ret == 0)
204 return 0;
205
206 this->playpos += num_frames;
207
208 return 1;
209 }
210
ao_sndio_delay(ao_driver_t * this_gen)211 static int ao_sndio_delay (ao_driver_t *this_gen)
212 {
213 sndio_driver_t *this = (sndio_driver_t *) this_gen;
214 int bufused;
215
216 if (this->realpos < 0)
217 bufused = this->playpos;
218 else
219 bufused = this->playpos - this->realpos;
220
221 return bufused;
222 }
223
ao_sndio_close(ao_driver_t * this_gen)224 static void ao_sndio_close(ao_driver_t *this_gen)
225 {
226 sndio_driver_t *this = (sndio_driver_t *) this_gen;
227
228 xprintf (this->xine, XINE_VERBOSITY_DEBUG,
229 "audio_sndio_out: ao_sndio_close called\n");
230
231 if (!sio_stop(this->hdl)) {
232 xprintf (this->xine, XINE_VERBOSITY_DEBUG,
233 "audio_sndio_out: ao_sndio_close could not stop\n");
234 }
235
236 sio_close(this->hdl);
237 this->hdl = NULL;
238 }
239
ao_sndio_get_capabilities(ao_driver_t * this_gen)240 static uint32_t ao_sndio_get_capabilities (ao_driver_t *this_gen)
241 {
242 sndio_driver_t *this = (sndio_driver_t *) this_gen;
243
244 return this->capabilities;
245 }
246
ao_sndio_exit(ao_driver_t * this_gen)247 static void ao_sndio_exit(ao_driver_t *this_gen)
248 {
249 sndio_driver_t *this = (sndio_driver_t *) this_gen;
250
251 xprintf (this->xine, XINE_VERBOSITY_DEBUG,
252 "audio_sndio_out: ao_sndio_exit called\n");
253
254 if (this->hdl != NULL)
255 sio_close(this->hdl);
256 }
257
ao_sndio_get_property(ao_driver_t * this_gen,int property)258 static int ao_sndio_get_property (ao_driver_t *this_gen, int property)
259 {
260 sndio_driver_t *this = (sndio_driver_t *) this_gen;
261
262 switch (property) {
263 case AO_PROP_MIXER_VOL:
264 return this->mixer.volume;
265 break;
266 case AO_PROP_MUTE_VOL:
267 return this->mixer.mute;
268 break;
269 }
270
271 return 0;
272 }
273
ao_sndio_set_property(ao_driver_t * this_gen,int property,int value)274 static int ao_sndio_set_property (ao_driver_t *this_gen, int property, int value)
275 {
276 sndio_driver_t *this = (sndio_driver_t *) this_gen;
277 int vol;
278
279 if (this->hdl == NULL)
280 return 0;
281
282 switch(property) {
283 case AO_PROP_MIXER_VOL:
284 this->mixer.volume = value;
285 if (!this->mixer.mute)
286 sio_setvol(this->hdl, PCT_TO_MIDI(this->mixer.volume));
287 return this->mixer.volume;
288 break;
289
290 case AO_PROP_MUTE_VOL:
291 this->mixer.mute = (value) ? 1 : 0;
292 vol = 0;
293 if (!this->mixer.mute)
294 vol = PCT_TO_MIDI(this->mixer.volume);
295 sio_setvol(this->hdl, vol);
296 return value;
297 break;
298 }
299
300 return value;
301 }
302
303 /*
304 * pause, resume, flush buffers
305 */
ao_sndio_ctrl(ao_driver_t * this_gen,int cmd,...)306 static int ao_sndio_ctrl(ao_driver_t *this_gen, int cmd, ...)
307 {
308 sndio_driver_t *this = (sndio_driver_t *) this_gen;
309
310 /*
311 * sndio pauses automatically if there are no more samples to play
312 * and resumes when there are samples again. So we leave this empty
313 * for the moment.
314 */
315
316 return 0;
317 }
318
open_plugin(audio_driver_class_t * class_gen,const void * data)319 static ao_driver_t *open_plugin (audio_driver_class_t *class_gen, const void *data)
320 {
321 sndio_class_t *class = (sndio_class_t *) class_gen;
322 sndio_driver_t *this;
323
324 lprintf ("audio_sndio_out: open_plugin called\n");
325
326 this = calloc(1, sizeof (sndio_driver_t));
327 if (!this)
328 return NULL;
329
330 this->xine = class->xine;
331
332 /*
333 * Set capabilities
334 */
335 this->capabilities = AO_CAP_MODE_MONO | AO_CAP_MODE_STEREO |
336 AO_CAP_MODE_4CHANNEL | AO_CAP_MODE_4_1CHANNEL |
337 AO_CAP_MODE_5CHANNEL | AO_CAP_MODE_5_1CHANNEL |
338 AO_CAP_MIXER_VOL | AO_CAP_MUTE_VOL | AO_CAP_8BITS |
339 AO_CAP_16BITS;
340
341 this->ao_driver.get_capabilities = ao_sndio_get_capabilities;
342 this->ao_driver.get_property = ao_sndio_get_property;
343 this->ao_driver.set_property = ao_sndio_set_property;
344 this->ao_driver.open = ao_sndio_open;
345 this->ao_driver.num_channels = ao_sndio_num_channels;
346 this->ao_driver.bytes_per_frame = ao_sndio_bytes_per_frame;
347 this->ao_driver.delay = ao_sndio_delay;
348 this->ao_driver.write = ao_sndio_write;
349 this->ao_driver.close = ao_sndio_close;
350 this->ao_driver.exit = ao_sndio_exit;
351 this->ao_driver.get_gap_tolerance = ao_sndio_get_gap_tolerance;
352 this->ao_driver.control = ao_sndio_ctrl;
353
354 return &this->ao_driver;
355 }
356
357 /*
358 * class functions
359 */
360
dispose_class(audio_driver_class_t * this_gen)361 static void dispose_class (audio_driver_class_t *this_gen)
362 {
363 sndio_class_t *this = (sndio_class_t *) this_gen;
364
365 free(this);
366 }
367
init_class(xine_t * xine,const void * data)368 static void *init_class (xine_t *xine, const void *data)
369 {
370 sndio_class_t *this;
371
372 lprintf ("audio_sndio_out: init class\n");
373
374 this = calloc(1, sizeof (sndio_class_t));
375 if (!this)
376 return NULL;
377
378 this->driver_class.open_plugin = open_plugin;
379 this->driver_class.identifier = "sndio";
380 this->driver_class.description = N_("xine audio output plugin using sndio audio devices/drivers ");
381 this->driver_class.dispose = dispose_class;
382
383 this->xine = xine;
384
385 return this;
386 }
387
388 static const ao_info_t ao_info_sndio = {
389 .priority = 12,
390 };
391
392 /*
393 * exported plugin catalog entry
394 */
395
396 const plugin_info_t xine_plugin_info[] EXPORTED = {
397 /* type, API, "name", version, special_info, init_function */
398 { PLUGIN_AUDIO_OUT, 9, "sndio", XINE_VERSION_CODE, &ao_info_sndio, init_class },
399 { PLUGIN_NONE, 0, NULL, 0, NULL, NULL }
400 };
401