1 /*
2  *
3  *  ao_arts.c
4  *
5  *      Copyright (C) Rik Hemsley (rikkus) <rik@kde.org> 2000
6  *     Modifications Copyright (C) 2010 Monty <monty@xiph.org>
7  *
8  *  This file is part of libao, a cross-platform library.  See
9  *  README for a history of this source code.
10  *
11  *  libao is free software; you can redistribute it and/or modify
12  *  it under the terms of the GNU General Public License as published by
13  *  the Free Software Foundation; either version 2, or (at your option)
14  *  any later version.
15  *
16  *  libao is distributed in the hope that it will be useful,
17  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
18  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  *  GNU General Public License for more details.
20  *
21  *  You should have received a copy of the GNU General Public License
22  *  along with GNU Make; see the file COPYING.  If not, write to
23  *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
24  *
25  ********************************************************************
26 
27  last mod: $Id: ao_arts.c 17718 2010-12-06 20:09:29Z xiphmont $
28 
29  ********************************************************************/
30 
31 #include <stdio.h>
32 #include <errno.h>
33 #include <string.h>
34 #include <pthread.h>
35 
36 #include <glib.h>
37 #include <artsc.h>
38 #include <ao/ao.h>
39 #include <ao/plugin.h>
40 
41 /* we must serialize all aRtsc library access as virtually every
42    operation accesses global state */
43 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
44 static int server_open_count = 0;
45 
46 static char *ao_arts_options[] = {"matrix","verbose","quiet","debug","multi"};
47 static ao_info ao_arts_info =
48 {
49 	AO_TYPE_LIVE,
50 	"aRts output",
51 	"arts",
52 	"Monty <monty@xiph.org>",
53 	"Outputs to the aRts soundserver.",
54 	AO_FMT_NATIVE,
55 #ifdef HAVE_ARTS_SUSPENDED
56 	45,
57 #else
58 	15,
59 #endif
60 	ao_arts_options,
61         sizeof(ao_arts_options)/sizeof(*ao_arts_options)
62 };
63 
64 typedef struct ao_arts_internal
65 {
66   arts_stream_t stream;
67   int allow_multi;
68   int buffersize;
69 } ao_arts_internal;
70 
71 
ao_plugin_test()72 int ao_plugin_test()
73 {
74   pthread_mutex_lock(&mutex);
75 
76   if (server_open_count || arts_init() == 0) {
77     server_open_count++;
78 
79 #ifdef HAVE_ARTS_SUSPENDED
80     if (arts_suspended() == 1) {
81       server_open_count--;
82       if(!server_open_count)arts_free();
83       pthread_mutex_unlock(&mutex);
84       return 0;
85     }
86 #endif
87     server_open_count--;
88     if(!server_open_count)arts_free();
89     arts_free();
90     pthread_mutex_unlock(&mutex);
91     return 1;
92   }
93   pthread_mutex_unlock(&mutex);
94   return 0;
95 }
96 
ao_plugin_driver_info(void)97 ao_info *ao_plugin_driver_info(void)
98 {
99   /* this is a dirty but necessary trick.  aRts's C library for
100      clients calls g_thread_init() internally in arts_init() whether
101      your app uses glib or not.  This call sets several
102      thread-specific keys and stashes glib static data state in the
103      calling thread.  Later, ao_close() calls arts_free(), glib is
104      dlclose()d, but the keys aren't deleted and this will cause a
105      segfault when the thread that originally called arts_init() exits
106      and pthreads tries to clean up. In addition, g_thread_init() must
107      be called outside of mutextes, and access to arts_init() below is
108      and must be locked.
109 
110      So we tackle this problem in two ways; one, call g_thread_init()
111      here, which will be during ao_initialize().  It is documented
112      that ao_initialize() must be called in the app's main
113      thread. Second, be sure to link with glib-2.0, which means that
114      the glib static context is never unloaded by aRts (this alone is
115      currently enough in practice to avoid problems, but that's partly
116      by accident. The g_thread_init() here avoids it randomly breaking
117      again in the future by following documentation exactly). */
118 
119   if (!g_thread_supported ())
120     g_thread_init(0);
121 
122   return &ao_arts_info;
123 }
124 
125 
ao_plugin_device_init(ao_device * device)126 int ao_plugin_device_init(ao_device *device)
127 {
128   ao_arts_internal *internal;
129 
130   internal = (ao_arts_internal *) calloc(1,sizeof(ao_arts_internal));
131 
132   if (internal == NULL)
133     return 0; /* Could not initialize device memory */
134 
135   device->internal = internal;
136   device->output_matrix_order = AO_OUTPUT_MATRIX_FIXED;
137   device->output_matrix=strdup("L,R");
138 
139   return 1; /* Memory alloc successful */
140 }
141 
142 
ao_plugin_set_option(ao_device * device,const char * key,const char * value)143 int ao_plugin_set_option(ao_device *device, const char *key, const char *value)
144 {
145   ao_arts_internal *internal = (ao_arts_internal *) device->internal;
146 
147   if (!strcmp(key, "multi")) {
148     if(!strcmp(value,"yes") || !strcmp(value,"y") ||
149        !strcmp(value,"true") || !strcmp(value,"t") ||
150        !strcmp(value,"1"))
151       {
152         internal->allow_multi = 1;
153         return 1;
154       }
155     if(!strcmp(value,"no") || !strcmp(value,"n") ||
156        !strcmp(value,"false") || !strcmp(value,"f") ||
157        !strcmp(value,"0"))
158       {
159         internal->allow_multi = 0;
160         return 1;
161       }
162     return 0;
163   }
164   return 1;
165 }
166 
ao_plugin_open(ao_device * device,ao_sample_format * format)167 int ao_plugin_open(ao_device *device, ao_sample_format *format)
168 {
169   ao_arts_internal *internal = (ao_arts_internal *) device->internal;
170   int errorcode=0;
171 
172   if(device->output_channels<1 || device->output_channels>2){
173     /* the docs aren't kidding here--- feed it more than 2
174        channels and the server simply stops answering; the
175        connection freezes. */
176     aerror("Cannot handle more than 2 channels\n");
177     return 0;
178   }
179 
180   pthread_mutex_lock(&mutex);
181   if(!server_open_count)
182     errorcode = arts_init();
183   else{
184     if(!internal->allow_multi){
185       /* multiple-playback access disallowed; it's disallowed by
186          default as it tends to crash the aRts server. */
187       adebug("Multiple-open access disallowed and playback already in progress.\n");
188       pthread_mutex_unlock(&mutex);
189       return 0;
190     }
191   }
192 
193   if (0 != errorcode){
194     pthread_mutex_unlock(&mutex);
195     aerror("Could not connect to server => %s.\n",arts_error_text(errorcode));
196     return 0; /* Could not connect to server */
197   }
198 
199   device->driver_byte_format = AO_FMT_NATIVE;
200   internal->stream = arts_play_stream(format->rate,
201                                       format->bits,
202                                       device->output_channels,
203                                       "libao stream");
204 
205   if(!internal->stream){
206     if(!server_open_count)arts_free();
207     pthread_mutex_unlock(&mutex);
208 
209     aerror("Could not open audio stream.\n");
210     return 0;
211   }
212 
213   if(arts_stream_set(internal->stream, ARTS_P_BLOCKING, 0)){
214     arts_close_stream(internal->stream);
215     internal->stream=NULL;
216     if(!server_open_count)arts_free();
217     pthread_mutex_unlock(&mutex);
218 
219     aerror("Could not set audio stream to nonblocking.\n");
220     return 0;
221   }
222 
223   if((internal->buffersize = arts_stream_get(internal->stream, ARTS_P_BUFFER_SIZE))<=0){
224     arts_close_stream(internal->stream);
225     internal->stream=NULL;
226     if(!server_open_count)arts_free();
227     pthread_mutex_unlock(&mutex);
228 
229     aerror("Could not get audio buffer size.\n");
230     return 0;
231   }
232 
233   server_open_count++;
234   pthread_mutex_unlock(&mutex);
235 
236   return 1;
237 }
238 
239 
ao_plugin_play(ao_device * device,const char * output_samples,uint_32 num_bytes)240 int ao_plugin_play(ao_device *device, const char *output_samples,
241 		uint_32 num_bytes)
242 {
243   ao_arts_internal *internal = (ao_arts_internal *) device->internal;
244   int spindetect=0;
245   int i;
246 
247   pthread_mutex_lock(&mutex);
248 
249   /* the while loop below is another dirty but servicable hack needed
250      for two reasons:
251 
252      1) for multiple-stream playback, there is no way to block on
253      more than one stream object at a time.  One can neither
254      select/poll, nor can we block on multiple writes at a time as
255      access to arts_write must be locked globally.  So we run in
256      nonblocking mode and write to the server based on audio timing.
257 
258      2) Although aRts allegedly delivers errors on write failure, I've
259      never observed it actually do so in practice.  Most of the time
260      when something goes wrong, it returns a short count or zero, but
261      there are also cases where the write simply blocks forever
262      because the server logged an error and stopped answering without
263      informing the client or dropping the connection.  Again, we have
264      to run in nonblocking moda and look for an output pattern that
265      indicates the server disappeared out from under us (no successful
266      writes over a period that should certainly have starved
267      playback) */
268 
269   while(1){
270     int accwrote=0;
271 
272     /* why the multiple rapid-fire writes below?
273 
274        aRts in nonblocking mode does not service internal buffering
275        state outside of the write call.  Further, the internal buffer
276        state appears to be pipelined; although the server may be
277        waiting or even starved for data, a non blocking write call
278        will often return immediately without actually writing
279        anything, regardless of internal buffer fullness.  Several more
280        calls (all returning 0, due to the full internal buffer) will
281        suddenly cause the internal state to actually flush data to the
282        server.  Thus the multiple writes in sequence are a way of
283        having the aRts internal state step through the sequence
284        necessary to actually submit data to the server.
285     */
286 
287     for(i=0;i<5;i++){
288       int wrote = arts_write(internal->stream, output_samples, num_bytes);
289       if(wrote < 0){
290         /* although it's vanishingly unlikely that aRtsc will actually
291            bother reporting any errors, we might as well be ready for
292            one. */
293         pthread_mutex_unlock(&mutex);
294         aerror("Write error\n");
295         return 0;
296       }
297       accwrote+=wrote;
298       num_bytes -= wrote;
299       output_samples += wrote;
300     }
301 
302     if(accwrote)
303       spindetect=0;
304     else
305       spindetect++;
306 
307     if(spindetect==100){
308         pthread_mutex_unlock(&mutex);
309         aerror("Write thread spinning; has the aRts server crashed?\n");
310         return 0;
311     }
312 
313     if(num_bytes>0){
314       long wait = internal->buffersize*1000/(device->output_channels*device->bytewidth*device->rate);
315       pthread_mutex_unlock(&mutex);
316       wait = (wait/8)*1000;
317       if(wait<1)wait=1;
318       if(wait>500000)wait=500000;
319       usleep(wait);
320       pthread_mutex_lock(&mutex);
321     }else{
322       pthread_mutex_unlock(&mutex);
323       break;
324     }
325   }
326 
327   return 1;
328 }
329 
330 
ao_plugin_close(ao_device * device)331 int ao_plugin_close(ao_device *device)
332 {
333   ao_arts_internal *internal = (ao_arts_internal *) device->internal;
334   pthread_mutex_lock(&mutex);
335   if(internal->stream)
336     arts_close_stream(internal->stream);
337   internal->stream = NULL;
338 
339   server_open_count--;
340   if(!server_open_count)arts_free();
341   pthread_mutex_unlock(&mutex);
342 
343   return 1;
344 }
345 
346 
ao_plugin_device_clear(ao_device * device)347 void ao_plugin_device_clear(ao_device *device)
348 {
349   ao_arts_internal *internal = (ao_arts_internal *) device->internal;
350 
351   if(internal)
352     free(internal);
353   device->internal=NULL;
354 }
355