Shut set but unused warnings.
[sxemacs] / src / media / sound-oss.c
1 /** sound-oss.c - play a sound file on using the (deprecated) OSS
2  **
3  ** Copyright (C) 2006 Sebastian Freundt
4  **
5  ** Copyright (C) 1995,96 by Markus Gutschke (gutschk@math.uni-muenster.de)
6  ** This is version 1.3 of linuxplay.c, with platform-independent functions
7  ** moved to a different file by Robert Bihlmeyer <robbe@orcus.priv.at>.
8  **
9  ** Parts of this code were inspired by sunplay.c, which is copyright 1989 by
10  ** Jef Poskanzer and 1991,92 by Jamie Zawinski; c.f. sunplay.c for further
11  ** information.
12  **
13  ** Permission to use, copy, modify, and distribute this software and its
14  ** documentation for any purpose and without fee is hereby granted, provided
15  ** that the above copyright notice appear in all copies and that both that
16  ** copyright notice and this permission notice appear in supporting
17  ** documentation.  This software is provided "as is" without express or
18  ** implied warranty.
19  **
20  */
21
22 /* Synched up with: Not in FSF. */
23
24 /* XEmacs beta testers say:  undef this by default. */
25 #undef NOVOLUMECTRLFORMULAW     /* Changing the volume for uLaw-encoded
26                                    samples sounds very poor; possibly,
27                                    this is true only for the PC-Snd
28                                    driver, so undefine this symbol at your
29                                    discretion */
30
31 #ifdef HAVE_CONFIG_H
32 #include <config.h>
33 #endif
34
35 #include <errno.h>
36 #include <fcntl.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <fcntl.h>
41 #include <sys/file.h>
42 #include <sys/ioctl.h>
43 #include <unistd.h>
44
45 #include "lisp.h"
46 #include "sysfile.h"
47
48 #include "media.h"
49 #include "sound-oss.h"
50
51 Lisp_Object Qoss;               /* cannot be Qnative */
52
53 #define MYSELF ADRIVER_OSS
54
55 #define __OSS_DEBUG__(args...)          fprintf(stderr, "OSS " args)
56 #ifndef OSS_DEBUG_FLAG
57 #define OSS_DEBUG(args...)
58 #else
59 #define OSS_DEBUG(args...)              __OSS_DEBUG__(args)
60 #endif
61 #define OSS_DEBUG_HW(args...)           OSS_DEBUG("[hardware]: " args)
62 #define OSS_DEBUG_S(args...)            OSS_DEBUG("[stream]: " args)
63 #define OSS_DEBUG_COE(args...)          OSS_DEBUG("[coerce]: " args)
64 #define OSS_DEBUG_AJ(args...)           OSS_DEBUG("[audio-job]: " args)
65 #define OSS_CRITICAL(args...)           __OSS_DEBUG__("CRITICAL: " args)
66
67 #define audio_dev "/dev/dsp"
68
69 \f
70 DECLARE_AUDIO_DEVICE_SIMPLE_METHS(sound_oss);
71 DEFINE_AUDIO_DEVICE_SIMPLE(sound_oss);
72
73 \f
74 static Lisp_Object
75 sound_oss_mark(ad_device_data *devdata)
76 {
77         sound_oss_data_t *sod = devdata;
78
79         mark_object(sod->device);
80
81         return Qnil;
82 }
83
84 static void
85 sound_oss_print(Lisp_Object device, Lisp_Object pcfun, int ef)
86 {
87         sound_oss_data_t *sod = NULL;
88
89         sod = get_audio_device_data(device);
90         /* cannot use incomplete or corrupt audio devices */
91         if (XAUDIO_DEVICE_DRIVER(device) != MYSELF || sod == NULL) {
92                 write_c_string(" VOID", pcfun);
93                 /* now that we are here, mark AO device as dead */
94                 XAUDIO_DEVICE_STATE(device) = ASTATE_DEAD;
95                 return;
96         }
97
98         /* info about the connected output plugin */
99         write_c_string(" :device ", pcfun);
100         if (NILP(sod->device))
101                 write_c_string("\"/dev/dsp\"", pcfun);
102         else
103                 print_internal(sod->device, pcfun, ef);
104
105         if (sod->lock) {
106                 write_c_string(" :busy t", pcfun);
107         } else
108                 write_c_string(" :busy nil", pcfun);
109
110         if (sod->keep_open) {
111                 write_c_string(" :keep-open t", pcfun);
112         } else
113                 write_c_string(" :keep-open nil", pcfun);
114
115         return;
116 }
117
118 \f
119 static int
120 sound_oss_open_device(sound_oss_data_t *sod)
121 {
122         if (sod->device_fd < 0) {
123                 /* open /dev/dsp */
124                 if ((sod->device_fd = open(audio_dev, O_WRONLY, 0)) < 0)
125                         return sod->device_fd;
126         }
127
128         return 0;
129 }
130
131 static int
132 sound_oss_close_device(sound_oss_data_t *sod)
133 {
134         sod->lock = 0;
135         if (sod->device_fd < 0)
136                 return 0;
137
138         if (sod->keep_open)
139                 return 0;
140
141         /* close /dev/dsp */
142         ioctl(sod->device_fd, SNDCTL_DSP_SYNC, NULL);
143         ioctl(sod->device_fd, SNDCTL_DSP_RESET, NULL);
144
145         OSS_DEBUG_HW("device file closed\n");
146         close(sod->device_fd);
147         sod->device_fd = -1;
148
149         return 0;
150 }
151
152 static int
153 sound_oss_init_device(sound_oss_data_t *sod, sound_oss_aj_data_t *sosd)
154 {
155         int tmp[] = {
156                 AFMT_S16_LE, AFMT_S16_BE, AFMT_U8,
157                 AFMT_QUERY };
158         int i, num_tmp = sizeof(tmp)/sizeof(int), fd = sod->device_fd;
159
160         if (ioctl(sod->device_fd, SNDCTL_DSP_SYNC, NULL) < 0) {
161                 OSS_DEBUG_HW("SNDCTL_DSP_SYNC failed.\n");
162                 return -1;
163         }
164
165         /* Initialize sound hardware with preferred parameters */
166
167         /* try to set channels */
168         sosd->channels = (sosd->mtap->channels) -1;
169         if (ioctl(sod->device_fd, SNDCTL_DSP_STEREO, &sosd->channels) < 0) {
170                 OSS_DEBUG_HW("cannot set channels\n");
171                 return -1;
172         }
173
174         if (++sosd->channels != sosd->mtap->channels) {
175                 if (sosd->channels == 1) {
176                         /* mono, source is stereo */
177                         ADD_MEDIA_SAMPLE_EFFECT(
178                                 sosd->coe_chain, sosd->coe_ch_cnt,
179                                 MEDIA_SAMPLE_EFFECT(sxe_mse_2ch_to_1ch), NULL);
180                                 OSS_DEBUG_COE("STEREO->MONO coerce.\n");
181                 } else if (sosd->channels == 2) {
182                         /* stereo, source is mono */
183                         ADD_MEDIA_SAMPLE_EFFECT(
184                                 sosd->coe_chain, sosd->coe_ch_cnt,
185                                 MEDIA_SAMPLE_EFFECT(sxe_mse_1ch_to_2ch), NULL);
186                                 OSS_DEBUG_COE("MONO->STEREO coerce.\n");
187                 } else {
188                         /* bullshit */
189                         OSS_DEBUG_HW("Hardware supports %d channels, "
190                                      "source has %d channels.\n",
191                                      sosd->channels, sosd->mtap->channels);
192                         sosd->channels = 0;
193                         return -1;
194                 }
195         }
196
197         /* we try some sample formats */
198         i = 0;
199         OSS_DEBUG("trying %d formats\n", num_tmp);
200         while (i < num_tmp && ioctl(fd, SNDCTL_DSP_SAMPLESIZE, &tmp[i]) < 0) {
201                 i++;
202         }
203
204         if (i == num_tmp) {
205                 OSS_DEBUG_HW("Your soundcard is bullshit.\n");
206                 return -1;
207         }
208
209         switch (tmp[i]) {
210         case AFMT_U8:
211                 OSS_DEBUG_HW("Using U8.\n");
212                 sosd->msf = sxe_msf_U8;
213                 sosd->framesize = sosd->channels * sizeof(uint8_t);
214                 break;
215         case AFMT_S16_LE:
216         case AFMT_S16_BE:
217                 OSS_DEBUG_HW("Using S16.\n");
218                 sosd->msf = sxe_msf_S16;
219                 sosd->framesize = sosd->channels * sizeof(int16_t);
220                 break;
221         default:
222                 OSS_DEBUG_HW(".oO{ I must not be here }\n");
223                 sosd->framesize = 0;
224                 return -1;
225                 break;
226         }
227
228         /* The PCSP driver does not support reading of the sampling rate via the
229            SOUND_PCM_READ_RATE ioctl; determine "the_speed" here */
230         sosd->samplerate = sosd->mtap->samplerate;
231         if (ioctl(sod->device_fd, SNDCTL_DSP_SPEED, &sosd->samplerate) < 0 ||
232             sosd->samplerate > 1.02 * sosd->mtap->samplerate ||
233             sosd->samplerate < 0.98 * sosd->mtap->samplerate) {
234                 OSS_DEBUG_HW("OSS cannot set rate: %d\n",
235                              sosd->samplerate);
236                 /* actually we should use the rerate effect */
237                 return -1;
238         }
239
240 #if 0                           /* stupid mixer device */
241         /* Use the mixer device for setting the playback volume */
242         if (sod->device_fd > 0) {
243                 int vol = 100;
244                 vol |= 256 * 100;
245                 /* Do not signal an error, if volume control is unavailable! */
246                 ioctl(sod->device_fd, SOUND_MIXER_WRITE_PCM, &vol);
247         }
248 #endif
249
250         return 1;
251 }
252
253 static ad_device_data *
254 sound_oss_create(Lisp_Object oss_options)
255 {
256         /* result */
257         sound_oss_data_t *sod = NULL;
258         int keep_open = 0;
259         /* option keywords */
260         Lisp_Object opt_device;
261         Lisp_Object opt_keepopen;
262
263         /* parse options */
264         opt_device = Fplist_get(oss_options, Q_device, Qnil);
265         if (!NILP(opt_device) && !STRINGP(opt_device)) {
266                 wrong_type_argument(Qstringp, opt_device);
267                 return NULL;
268         }
269
270         opt_keepopen = Fplist_get(oss_options, Q_keep_open, Qnil);
271         if (!NILP(opt_keepopen))
272                 keep_open = 1;
273
274         /* initialise and fill */
275         sod = xnew_and_zero(sound_oss_data_t);
276         sod->device = opt_device;
277         sod->keep_open = keep_open;
278         sod->device_fd = -1;
279         SXE_MUTEX_INIT(&sod->mtx);
280
281         /* Open the device */
282
283
284         if (!keep_open) {
285                 sod->device_fd = -1;
286         }
287
288         return (ad_device_data*)sod;
289 }
290
291 static void
292 sound_oss_finish(ad_device_data *data)
293 {
294         sound_oss_data_t *sod = data;
295
296         sod->lock = 1;
297         sod->keep_open = 0;
298         sound_oss_close_device(sod);
299         SXE_MUTEX_FINI(&sod->mtx);
300
301         OSS_DEBUG("audio-device finished.\n");
302
303         return;
304 }
305 \f
306 #ifdef EF_USE_ASYNEQ
307 static inline void
308 sound_oss_change_volume(audio_job_t aj, audio_job_event_args_t args)
309 {
310         SXE_MUTEX_LOCK(&aj->mtx);
311         aj->volume = args->volume_args;
312         SXE_MUTEX_UNLOCK(&aj->mtx);
313 }
314
315 static inline void
316 sound_oss_change_rate(audio_job_t aj, audio_job_event_args_t args)
317 {
318         SXE_MUTEX_LOCK(&aj->mtx);
319         aj->ratetrafo = args->rate_args;
320         SXE_MUTEX_UNLOCK(&aj->mtx);
321 }
322
323 static inline void
324 sound_oss_change_state(audio_job_t aj, audio_job_event_args_t args)
325 {
326         SXE_MUTEX_LOCK(&aj->mtx);
327         switch (args->state_args) {
328         case aj_pause:
329                 OSS_DEBUG_AJ("->pause state\n");
330                 aj->play_state = MTPSTATE_PAUSE;
331                 break;
332         case aj_resume:
333                 OSS_DEBUG_AJ("->resume state\n");
334                 aj->play_state = MTPSTATE_RUN;
335                 break;
336         case aj_start:
337                 OSS_DEBUG_AJ("->start state\n");
338                 break;
339         case aj_stop:
340                 OSS_DEBUG_AJ("->stop state\n");
341                 aj->play_state = MTPSTATE_STOP;
342                 break;
343         case no_audio_job_change_states:
344         default:
345                 OSS_DEBUG_AJ("->unknown state\n");
346                 break;
347         }
348         SXE_MUTEX_UNLOCK(&aj->mtx);
349 }
350
351 static inline void
352 sound_oss_handle_aj_events(audio_job_t aj)
353         __attribute__((always_inline));
354 static inline void
355 sound_oss_handle_aj_events(audio_job_t aj)
356 {
357         sound_oss_aj_data_t *sasd;
358         audio_job_event_t ev = NULL;
359
360 #if 0
361         assert(audio_job_queue(aj));
362 #endif
363
364         SXE_MUTEX_LOCK(&aj->mtx);
365         sasd = audio_job_device_data(aj);
366         SXE_SET_UNUSED(sasd);
367
368         if ((ev = eq_noseeum_dequeue(audio_job_queue(aj))) == NULL) {
369                 SXE_MUTEX_UNLOCK(&aj->mtx);
370                 return;
371         }
372         SXE_MUTEX_UNLOCK(&aj->mtx);
373
374         OSS_DEBUG_AJ("Event 0x%lx\n", (long unsigned int)ev);
375         switch (audio_job_event_kind(ev)) {
376         case aj_change_state:
377                 OSS_DEBUG_AJ("change state event\n");
378                 sound_oss_change_state(aj, &audio_job_event_args(ev));
379                 break;
380         case aj_change_volume:
381                 OSS_DEBUG_AJ("change volume event\n");
382                 sound_oss_change_volume(aj, &audio_job_event_args(ev));
383                 break;
384         case aj_change_rate:
385                 OSS_DEBUG_AJ("change rate event\n");
386                 sound_oss_change_rate(aj, &audio_job_event_args(ev));
387                 break;
388         case no_audio_job_event_kinds:
389         default:
390                 OSS_CRITICAL("unknown event\n");
391                 break;
392         }
393         free_audio_job_event(ev);
394 }
395 #endif  /* EF_USE_ASYNEQ */
396 \f
397 static int
398 sound_oss_play(audio_job_t aj)
399 {
400         /* stream stuff */
401         Lisp_Media_Stream *ms;
402         media_substream *mss;
403         /* thread stuff */
404         media_thread_play_state mtp;
405         /* device stuff */
406         Lisp_Object device;
407         Lisp_Audio_Device *lad = NULL;
408         sound_oss_data_t *sod = NULL;
409         int fd;
410         /* buffering */
411         size_t len, tmplen;
412         sxe_media_sample_t *tmpbuf;
413         char *bptr = NULL;
414         size_t natlen;
415         int32_t written;
416         int resolution, i;
417         /* subthread stuff */
418         sound_oss_aj_data_t _sosd, *sosd = &_sosd;
419         sxe_mse_volume_args _volargs, *volargs = &_volargs;
420         sxe_mse_rerate_args _rrargs, *rrargs = &_rrargs;
421         /* cache stuff */
422         int alloced_myself = 0;
423
424         SOUND_UNPACK_MT(aj, device, ms, mss, lad, sod, sosd->mtap);
425
426         SXE_MUTEX_LOCK(&sod->mtx);
427         if (sod->lock) {
428                 OSS_DEBUG_HW("Device locked.\n");
429                 message(GETTEXT("audio-oss: "
430                                 "Device locked."));
431                 /* this lock is probably unnecessary */
432                 SXE_MUTEX_UNLOCK(&sod->mtx);
433                 return 0;
434         }
435
436         sod->lock = 1;
437
438         /* okay, njsf said /dev/dsp writing is not mt safe,
439          * also i hate OSS, so let's block everything here :) -hroptatyr
440          */
441 #if defined(HAVE_THREADS) && 0
442         pthread_mutex_lock(&mss->substream_mutex);
443 #endif
444
445         if (sound_oss_open_device(sod) < 0) {
446                 OSS_DEBUG_HW("Opening device failed.\n");
447                 sod->device_fd = -1;
448                 /* warning? */
449                 message(GETTEXT("audio-oss: "
450                                 "Opening OSS device failed."));
451                 sound_oss_close_device(sod);
452                 SXE_MUTEX_UNLOCK(&sod->mtx);
453                 return 0;
454         }
455
456         /* init the sosd */
457         sosd->paused = sosd->volume = 0;
458         sosd->samplerate = sosd->channels = 0;
459         sosd->coe_ch_cnt = 0;
460
461         if (sound_oss_init_device(sod, sosd) < 0) {
462                 OSS_DEBUG_HW("Device not configurable.\n");
463                 /* warning? */
464                 message(GETTEXT("audio-oss: "
465                                 "Cannot access OSS device."));
466                 sound_oss_close_device(sod);
467                 SXE_MUTEX_UNLOCK(&sod->mtx);
468                 return 0;
469         }
470         if(sosd->channels==0) {
471                 message(GETTEXT("audio-oss: "
472                                 "No channels."));
473                 sound_oss_close_device(sod);
474                 SXE_MUTEX_UNLOCK(&sod->mtx);
475                 return 0;
476         }
477
478
479         /* the volume effect */
480         ADD_MEDIA_SAMPLE_EFFECT(
481                 sosd->coe_chain, sosd->coe_ch_cnt,
482                 MEDIA_SAMPLE_EFFECT(sxe_mse_volume), volargs);
483         volargs->num_channels = sosd->channels;
484
485         /* the rerate effect */
486         ADD_MEDIA_SAMPLE_EFFECT(
487                 sosd->coe_chain, sosd->coe_ch_cnt,
488                 MEDIA_SAMPLE_EFFECT(sxe_mse_rerate), rrargs);
489         rrargs->num_channels = sosd->channels;
490         rrargs->srcrate = rrargs->tgtrate = 1;
491
492         OSS_DEBUG_COE("have %d coerce functions in my chain.\n",
493                       sosd->coe_ch_cnt);
494
495
496         XAUDIO_DEVICE_STATE(device) = ASTATE_ALIVE;
497         SXE_MUTEX_UNLOCK(&sod->mtx);
498
499         /* rewind the stream */
500         media_stream_meth(ms, rewind)(mss);
501
502         /* play chunks of the stream */
503         SXE_MUTEX_LOCK(&aj->mtx);
504         if (aj->buffer_alloc_size < SOUND_MAX_AUDIO_FRAME_SIZE) {
505                 alloced_myself = 1;
506                 aj->buffer = xmalloc_atomic(SOUND_MAX_AUDIO_FRAME_SIZE);
507                 aj->buffer_alloc_size = SOUND_MAX_AUDIO_FRAME_SIZE;
508         }
509         tmpbuf = (sxe_media_sample_t*)aj->buffer;
510         resolution = (sosd->mtap->samplerate * MTPSTATE_REACT_TIME) / 1000000;
511         fd = sod->device_fd;
512         natlen = 0;
513         SXE_MUTEX_UNLOCK(&aj->mtx);
514
515         while (aj->play_state != MTPSTATE_STOP) {
516
517 #ifdef EF_USE_ASYNEQ
518                 if (audio_job_queue(aj)) {
519                         sound_oss_handle_aj_events(aj);
520                 }
521 #endif
522
523                 SXE_MUTEX_LOCK(&aj->mtx);
524                 mtp = aj->play_state;
525                 SXE_MUTEX_UNLOCK(&aj->mtx);
526                 switch (mtp) {
527                 case MTPSTATE_RUN:
528                         if (natlen > 0)
529                                 goto write_buf;
530
531                         /* otherwise we simply fetch a new bunch of samples */
532                         len = media_stream_meth(ms, read)(
533                                 mss, aj->buffer, resolution);
534                         if (!len) {
535                                 OSS_DEBUG_S("finished\n");
536                                 SXE_MUTEX_LOCK(&aj->mtx);
537                                 aj->play_state = MTPSTATE_STOP;
538                                 SXE_MUTEX_UNLOCK(&aj->mtx);
539                                 break;
540                         }
541                         /* set up the volume args */
542                         volargs->volume[0] = volargs->volume[1] =
543                                 aj->volume;
544                         /* set up the rerate args */
545                         rrargs->tweak = aj->ratetrafo;
546
547                         /* coerce the stuff */
548                         tmplen = sosd->channels*len;
549                         for (i = 0; i < sosd->coe_ch_cnt; i++) {
550                                 OSS_DEBUG_COE("calling coerce "
551                                               "%d on b:0x%x l:%d\n",
552                                               i, (unsigned int)tmpbuf, tmplen);
553                                 tmplen = CALL_MEDIA_SAMPLE_EFFECT(
554                                         sosd->coe_chain, i,
555                                         tmpbuf, tmpbuf, tmplen);
556                         }
557
558                         /* bring back to S16 or U8 */
559                         MEDIA_SAMPLE_FORMAT_DOWNSAMPLE(sosd->msf)(
560                                 aj->buffer, aj->buffer, tmplen);
561
562                         /* convert tmplen back to number of frames */
563                         natlen = tmplen * sosd->framesize / sosd->channels;
564                         bptr = aj->buffer;
565
566                 write_buf:
567                         OSS_DEBUG_S("remaining cruft: %d bytes\n", natlen);
568                         if ((written = write(fd, bptr, natlen)) < 0) {
569                                 OSS_DEBUG_S("ERROR in write()\n");
570                                 natlen = 0;
571                         } else if (written) {
572                                 natlen -= written;
573                                 bptr += written;
574                         } else {
575                                 natlen = 0;
576                                 ioctl(fd, SNDCTL_DSP_SYNC, NULL);
577                         }
578                         break;
579                 case MTPSTATE_PAUSE:
580                         OSS_DEBUG("sleeping for %d\n", resolution);
581                         usleep(resolution);
582                         break;
583                 case MTPSTATE_UNKNOWN:
584                 case MTPSTATE_STOP:
585                 case NUMBER_OF_MEDIA_THREAD_PLAY_STATES:
586                 default:
587                         OSS_DEBUG("ACK, quit\n");
588                         SXE_MUTEX_LOCK(&aj->mtx);
589                         aj->play_state = MTPSTATE_STOP;
590                         SXE_MUTEX_UNLOCK(&aj->mtx);
591                         break;
592                 }
593         }
594
595         /* Now cleanup all used resources */
596         bptr = NULL;
597         SXE_MUTEX_LOCK(&aj->mtx);
598         if (alloced_myself && aj->buffer) {
599                 xfree(aj->buffer);
600         }
601         aj->buffer = NULL;
602         aj->buffer_alloc_size = 0;
603         SXE_MUTEX_UNLOCK(&aj->mtx);
604
605         sound_oss_close_device(sod);
606
607 #if defined(HAVE_THREADS) && 0
608         pthread_mutex_unlock(&mss->substream_mutex);
609 #endif
610
611         return 1;
612 }
613
614 #undef MYSELF