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