SDL-1.2.14
[sdl_omap.git] / src / audio / nto / SDL_nto_audio.c
1 /*
2     SDL - Simple DirectMedia Layer
3     Copyright (C) 1997-2009 Sam Lantinga
4
5     This library is free software; you can redistribute it and/or
6     modify it under the terms of the GNU Library General Public
7     License as published by the Free Software Foundation; either
8     version 2 of the License, or (at your option) any later version.
9
10     This library is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13     Library General Public License for more details.
14
15     You should have received a copy of the GNU Library General Public
16     License along with this library; if not, write to the Free
17     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18
19     Sam Lantinga
20     slouken@libsdl.org
21 */
22 #include "SDL_config.h"
23
24 #include <errno.h>
25 #include <unistd.h>
26 #include <fcntl.h>
27 #include <signal.h>
28 #include <sys/types.h>
29 #include <sys/time.h>
30 #include <sched.h>
31 #include <sys/select.h>
32 #include <sys/neutrino.h>
33 #include <sys/asoundlib.h>
34
35 #include "SDL_timer.h"
36 #include "SDL_audio.h"
37 #include "../SDL_audiomem.h"
38 #include "../SDL_audio_c.h"
39 #include "SDL_nto_audio.h"
40
41 /* The tag name used by NTO audio */
42 #define DRIVER_NAME "qsa-nto"
43
44 /* default channel communication parameters */
45 #define DEFAULT_CPARAMS_RATE 22050
46 #define DEFAULT_CPARAMS_VOICES 1
47 /* FIXME: need to add in the near future flexible logic with frag_size and frags count */
48 #define DEFAULT_CPARAMS_FRAG_SIZE 4096
49 #define DEFAULT_CPARAMS_FRAGS_MIN 1
50 #define DEFAULT_CPARAMS_FRAGS_MAX 1
51
52 /* Open the audio device for playback, and don't block if busy */
53 #define OPEN_FLAGS SND_PCM_OPEN_PLAYBACK
54
55 #define QSA_NO_WORKAROUNDS  0x00000000
56 #define QSA_MMAP_WORKAROUND 0x00000001
57
58 struct BuggyCards
59 {
60    char* cardname;
61    unsigned long bugtype;
62 };
63
64 #define QSA_WA_CARDS 3
65
66 struct BuggyCards buggycards[QSA_WA_CARDS]=
67 {
68    {"Sound Blaster Live!", QSA_MMAP_WORKAROUND},
69    {"Vortex 8820",         QSA_MMAP_WORKAROUND},
70    {"Vortex 8830",         QSA_MMAP_WORKAROUND},
71 };
72
73 /* Audio driver functions */
74 static void NTO_ThreadInit(_THIS);
75 static int NTO_OpenAudio(_THIS, SDL_AudioSpec* spec);
76 static void NTO_WaitAudio(_THIS);
77 static void NTO_PlayAudio(_THIS);
78 static Uint8* NTO_GetAudioBuf(_THIS);
79 static void NTO_CloseAudio(_THIS);
80
81 /* card names check to apply the workarounds */
82 static int NTO_CheckBuggyCards(_THIS, unsigned long checkfor)
83 {
84     char scardname[33];
85     int it;
86     
87     if (snd_card_get_name(cardno, scardname, 32)<0)
88     {
89         return 0;
90     }
91
92     for (it=0; it<QSA_WA_CARDS; it++)
93     {
94        if (SDL_strcmp(buggycards[it].cardname, scardname)==0)
95        {
96           if (buggycards[it].bugtype==checkfor)
97           {
98               return 1;
99           }
100        }
101     }
102
103     return 0;
104 }
105
106 static void NTO_ThreadInit(_THIS)
107 {
108    int status;
109    struct sched_param param;
110
111    /* increasing default 10 priority to 25 to avoid jerky sound */
112    status=SchedGet(0, 0, &param);
113    param.sched_priority=param.sched_curpriority+15;
114    status=SchedSet(0, 0, SCHED_NOCHANGE, &param);
115 }
116
117 /* PCM transfer channel parameters initialize function */
118 static void NTO_InitAudioParams(snd_pcm_channel_params_t* cpars)
119 {
120     SDL_memset(cpars, 0, sizeof(snd_pcm_channel_params_t));
121
122     cpars->channel = SND_PCM_CHANNEL_PLAYBACK;
123     cpars->mode = SND_PCM_MODE_BLOCK;
124     cpars->start_mode = SND_PCM_START_DATA;
125     cpars->stop_mode  = SND_PCM_STOP_STOP;
126     cpars->format.format = SND_PCM_SFMT_S16_LE;
127     cpars->format.interleave = 1;
128     cpars->format.rate = DEFAULT_CPARAMS_RATE;
129     cpars->format.voices = DEFAULT_CPARAMS_VOICES;
130     cpars->buf.block.frag_size = DEFAULT_CPARAMS_FRAG_SIZE;
131     cpars->buf.block.frags_min = DEFAULT_CPARAMS_FRAGS_MIN;
132     cpars->buf.block.frags_max = DEFAULT_CPARAMS_FRAGS_MAX;
133 }
134
135 static int NTO_AudioAvailable(void)
136 {
137     /*  See if we can open a nonblocking channel.
138         Return value '1' means we can.
139         Return value '0' means we cannot. */
140
141     int available;
142     int rval;
143     snd_pcm_t* handle;
144
145     available = 0;
146     handle = NULL;
147
148     rval = snd_pcm_open_preferred(&handle, NULL, NULL, OPEN_FLAGS);
149
150     if (rval >= 0)
151     {
152         available = 1;
153
154         if ((rval = snd_pcm_close(handle)) < 0)
155         {
156             SDL_SetError("NTO_AudioAvailable(): snd_pcm_close failed: %s\n", snd_strerror(rval));
157             available = 0;
158         }
159     }
160     else
161     {
162         SDL_SetError("NTO_AudioAvailable(): there are no available audio devices.\n");
163     }
164
165     return (available);
166 }
167
168 static void NTO_DeleteAudioDevice(SDL_AudioDevice *device)
169 {
170     if ((device)&&(device->hidden))
171     {
172         SDL_free(device->hidden);
173     }
174     if (device)
175     {
176         SDL_free(device);
177     }
178 }
179
180 static SDL_AudioDevice* NTO_CreateAudioDevice(int devindex)
181 {
182     SDL_AudioDevice *this;
183
184     /* Initialize all variables that we clean on shutdown */
185     this = (SDL_AudioDevice *)SDL_malloc(sizeof(SDL_AudioDevice));
186     if (this)
187     {
188         SDL_memset(this, 0, sizeof(SDL_AudioDevice));
189         this->hidden = (struct SDL_PrivateAudioData *)SDL_malloc(sizeof(struct SDL_PrivateAudioData));
190     }
191     if ((this == NULL) || (this->hidden == NULL))
192     {
193         SDL_OutOfMemory();
194         if (this)
195         {
196             SDL_free(this);
197         }
198         return (0);
199     }
200     SDL_memset(this->hidden, 0, sizeof(struct SDL_PrivateAudioData));
201     audio_handle = NULL;
202
203     /* Set the function pointers */
204     this->ThreadInit = NTO_ThreadInit;
205     this->OpenAudio = NTO_OpenAudio;
206     this->WaitAudio = NTO_WaitAudio;
207     this->PlayAudio = NTO_PlayAudio;
208     this->GetAudioBuf = NTO_GetAudioBuf;
209     this->CloseAudio = NTO_CloseAudio;
210
211     this->free = NTO_DeleteAudioDevice;
212
213     return this;
214 }
215
216 AudioBootStrap QNXNTOAUDIO_bootstrap =
217 {
218     DRIVER_NAME, "QNX6 QSA-NTO Audio",
219     NTO_AudioAvailable,
220     NTO_CreateAudioDevice
221 };
222
223 /* This function waits until it is possible to write a full sound buffer */
224 static void NTO_WaitAudio(_THIS)
225 {
226     fd_set wfds;
227     int selectret;
228
229     FD_ZERO(&wfds);
230     FD_SET(audio_fd, &wfds);
231
232     do {
233         selectret=select(audio_fd + 1, NULL, &wfds, NULL, NULL);
234         switch (selectret)
235         {
236             case -1:
237             case  0: SDL_SetError("NTO_WaitAudio(): select() failed: %s\n", strerror(errno));
238                      return;
239             default: if (FD_ISSET(audio_fd, &wfds))
240                      {
241                          return;
242                      }
243                      break;
244         }
245     } while(1);
246 }
247
248 static void NTO_PlayAudio(_THIS)
249 {
250     int written, rval;
251     int towrite;
252     void* pcmbuffer;
253
254     if (!this->enabled)
255     {
256         return;
257     }
258     
259     towrite = this->spec.size;
260     pcmbuffer = pcm_buf;
261
262     /* Write the audio data, checking for EAGAIN (buffer full) and underrun */
263     do {
264         written = snd_pcm_plugin_write(audio_handle, pcm_buf, towrite);
265         if (written != towrite)
266         {
267             if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
268             {
269                 /* Let a little CPU time go by and try to write again */
270                 SDL_Delay(1);
271                 /* if we wrote some data */
272                 towrite -= written;
273                 pcmbuffer += written * this->spec.channels;
274                 continue;
275             }           
276             else
277             {
278                 if ((errno == EINVAL) || (errno == EIO))
279                 {
280                     SDL_memset(&cstatus, 0, sizeof(cstatus));
281                     cstatus.channel = SND_PCM_CHANNEL_PLAYBACK;
282                     if ((rval = snd_pcm_plugin_status(audio_handle, &cstatus)) < 0)
283                     {
284                         SDL_SetError("NTO_PlayAudio(): snd_pcm_plugin_status failed: %s\n", snd_strerror(rval));
285                         return;
286                     }   
287                     if ((cstatus.status == SND_PCM_STATUS_UNDERRUN) || (cstatus.status == SND_PCM_STATUS_READY))
288                     {
289                         if ((rval = snd_pcm_plugin_prepare(audio_handle, SND_PCM_CHANNEL_PLAYBACK)) < 0)
290                         {
291                             SDL_SetError("NTO_PlayAudio(): snd_pcm_plugin_prepare failed: %s\n", snd_strerror(rval));
292                             return;
293                         }
294                     }                                   
295                     continue;
296                 }
297                 else
298                 {
299                     return;
300                 }
301             }
302         }
303         else
304         {
305             /* we wrote all remaining data */
306             towrite -= written;
307             pcmbuffer += written * this->spec.channels;
308         }
309     } while ((towrite > 0)  && (this->enabled));
310
311     /* If we couldn't write, assume fatal error for now */
312     if (towrite != 0)
313     {
314         this->enabled = 0;
315     }
316
317     return;
318 }
319
320 static Uint8* NTO_GetAudioBuf(_THIS)
321 {
322     return pcm_buf;
323 }
324
325 static void NTO_CloseAudio(_THIS)
326 {
327     int rval;
328
329     this->enabled = 0;
330
331     if (audio_handle != NULL)
332     {
333         if ((rval = snd_pcm_plugin_flush(audio_handle, SND_PCM_CHANNEL_PLAYBACK)) < 0)
334         {
335             SDL_SetError("NTO_CloseAudio(): snd_pcm_plugin_flush failed: %s\n", snd_strerror(rval));
336             return;
337         }
338         if ((rval = snd_pcm_close(audio_handle)) < 0)
339         {
340             SDL_SetError("NTO_CloseAudio(): snd_pcm_close failed: %s\n",snd_strerror(rval));
341             return;
342         }
343         audio_handle = NULL;
344     }
345 }
346
347 static int NTO_OpenAudio(_THIS, SDL_AudioSpec* spec)
348 {
349     int rval;
350     int format;
351     Uint16 test_format;
352     int found;
353
354     audio_handle = NULL;
355     this->enabled = 0;
356
357     if (pcm_buf != NULL)
358     {
359         SDL_FreeAudioMem(pcm_buf); 
360         pcm_buf = NULL;
361     }
362
363     /* initialize channel transfer parameters to default */
364     NTO_InitAudioParams(&cparams);
365
366     /* Open the audio device */
367     rval = snd_pcm_open_preferred(&audio_handle, &cardno, &deviceno, OPEN_FLAGS);
368     if (rval < 0)
369     {
370         SDL_SetError("NTO_OpenAudio(): snd_pcm_open failed: %s\n", snd_strerror(rval));
371         return (-1);
372     }
373
374     if (!NTO_CheckBuggyCards(this, QSA_MMAP_WORKAROUND))
375     {
376         /* enable count status parameter */
377         if ((rval = snd_pcm_plugin_set_disable(audio_handle, PLUGIN_DISABLE_MMAP)) < 0)
378         {
379             SDL_SetError("snd_pcm_plugin_set_disable failed: %s\n", snd_strerror(rval));
380             return (-1);
381         }
382     }
383
384     /* Try for a closest match on audio format */
385     format = 0;
386     /* can't use format as SND_PCM_SFMT_U8 = 0 in nto */
387     found = 0;
388
389     for (test_format=SDL_FirstAudioFormat(spec->format); !found ;)
390     {
391         /* if match found set format to equivalent ALSA format */
392         switch (test_format)
393         {
394             case AUDIO_U8:
395                            format = SND_PCM_SFMT_U8;
396                            found = 1;
397                            break;
398             case AUDIO_S8:
399                            format = SND_PCM_SFMT_S8;
400                            found = 1;
401                            break;
402             case AUDIO_S16LSB:
403                            format = SND_PCM_SFMT_S16_LE;
404                            found = 1;
405                            break;
406             case AUDIO_S16MSB:
407                            format = SND_PCM_SFMT_S16_BE;
408                            found = 1;
409                            break;
410             case AUDIO_U16LSB:
411                            format = SND_PCM_SFMT_U16_LE;
412                            found = 1;
413                            break;
414             case AUDIO_U16MSB:
415                            format = SND_PCM_SFMT_U16_BE;
416                            found = 1;
417                            break;
418             default:
419                            break;
420         }
421
422         if (!found)
423         {
424             test_format = SDL_NextAudioFormat();
425         }
426     }
427
428     /* assumes test_format not 0 on success */
429     if (test_format == 0)
430     {
431         SDL_SetError("NTO_OpenAudio(): Couldn't find any hardware audio formats");
432         return (-1);
433     }
434
435     spec->format = test_format;
436
437     /* Set the audio format */
438     cparams.format.format = format;
439
440     /* Set mono or stereo audio (currently only two channels supported) */
441     cparams.format.voices = spec->channels;
442         
443     /* Set rate */
444     cparams.format.rate = spec->freq;
445
446     /* Setup the transfer parameters according to cparams */
447     rval = snd_pcm_plugin_params(audio_handle, &cparams);
448     if (rval < 0)
449     {
450         SDL_SetError("NTO_OpenAudio(): snd_pcm_channel_params failed: %s\n", snd_strerror(rval));
451         return (-1);
452     }
453
454     /* Make sure channel is setup right one last time */
455     SDL_memset(&csetup, 0x00, sizeof(csetup));
456     csetup.channel = SND_PCM_CHANNEL_PLAYBACK;
457     if (snd_pcm_plugin_setup(audio_handle, &csetup) < 0)
458     {
459         SDL_SetError("NTO_OpenAudio(): Unable to setup playback channel\n");
460         return -1;
461     }
462
463
464     /* Calculate the final parameters for this audio specification */
465     SDL_CalculateAudioSpec(spec);
466
467     pcm_len = spec->size;
468
469     if (pcm_len==0)
470     {
471         pcm_len = csetup.buf.block.frag_size * spec->channels * (snd_pcm_format_width(format)/8);
472     }
473
474     /* Allocate memory to the audio buffer and initialize with silence (Note that
475        buffer size must be a multiple of fragment size, so find closest multiple)
476     */
477     pcm_buf = (Uint8*)SDL_AllocAudioMem(pcm_len);
478     if (pcm_buf == NULL)
479     {
480         SDL_SetError("NTO_OpenAudio(): pcm buffer allocation failed\n");
481         return (-1);
482     }
483     SDL_memset(pcm_buf, spec->silence, pcm_len);
484
485     /* get the file descriptor */
486     if ((audio_fd = snd_pcm_file_descriptor(audio_handle, SND_PCM_CHANNEL_PLAYBACK)) < 0)
487     {
488         SDL_SetError("NTO_OpenAudio(): snd_pcm_file_descriptor failed with error code: %s\n", snd_strerror(rval));
489         return (-1);
490     }
491
492     /* Trigger audio playback */
493     rval = snd_pcm_plugin_prepare(audio_handle, SND_PCM_CHANNEL_PLAYBACK);
494     if (rval < 0)
495     {
496         SDL_SetError("snd_pcm_plugin_prepare failed: %s\n", snd_strerror(rval));
497         return (-1);
498     }
499
500     this->enabled = 1;
501
502     /* Get the parent process id (we're the parent of the audio thread) */
503     parent = getpid();
504
505     /* We're really ready to rock and roll. :-) */
506     return (0);
507 }