Minor package-get cleanup + bldchain tweak
[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         if ((ev = eq_noseeum_dequeue(audio_job_queue(aj))) == NULL) {
367                 SXE_MUTEX_UNLOCK(&aj->mtx);
368                 return;
369         }
370         SXE_MUTEX_UNLOCK(&aj->mtx);
371
372         OSS_DEBUG_AJ("Event 0x%lx\n", (long unsigned int)ev);
373         switch (audio_job_event_kind(ev)) {
374         case aj_change_state:
375                 OSS_DEBUG_AJ("change state event\n");
376                 sound_oss_change_state(aj, &audio_job_event_args(ev));
377                 break;
378         case aj_change_volume:
379                 OSS_DEBUG_AJ("change volume event\n");
380                 sound_oss_change_volume(aj, &audio_job_event_args(ev));
381                 break;
382         case aj_change_rate:
383                 OSS_DEBUG_AJ("change rate event\n");
384                 sound_oss_change_rate(aj, &audio_job_event_args(ev));
385                 break;
386         case no_audio_job_event_kinds:
387         default:
388                 OSS_CRITICAL("unknown event\n");
389                 break;
390         }
391         free_audio_job_event(ev);
392 }
393 #endif  /* EF_USE_ASYNEQ */
394 \f
395 static int
396 sound_oss_play(audio_job_t aj)
397 {
398         /* stream stuff */
399         Lisp_Media_Stream *ms;
400         media_substream *mss;
401         /* thread stuff */
402         media_thread_play_state mtp;
403         /* device stuff */
404         Lisp_Object device;
405         Lisp_Audio_Device *lad = NULL;
406         sound_oss_data_t *sod = NULL;
407         int fd;
408         /* buffering */
409         size_t len, tmplen;
410         sxe_media_sample_t *tmpbuf;
411         char *bptr = NULL;
412         size_t natlen;
413         int32_t written;
414         int resolution, i;
415         /* subthread stuff */
416         sound_oss_aj_data_t _sosd, *sosd = &_sosd;
417         sxe_mse_volume_args _volargs, *volargs = &_volargs;
418         sxe_mse_rerate_args _rrargs, *rrargs = &_rrargs;
419         /* cache stuff */
420         int alloced_myself = 0;
421
422         SOUND_UNPACK_MT(aj, device, ms, mss, lad, sod, sosd->mtap);
423
424         SXE_MUTEX_LOCK(&sod->mtx);
425         if (sod->lock) {
426                 OSS_DEBUG_HW("Device locked.\n");
427                 message(GETTEXT("audio-oss: "
428                                 "Device locked."));
429                 /* this lock is probably unnecessary */
430                 SXE_MUTEX_UNLOCK(&sod->mtx);
431                 return 0;
432         }
433
434         sod->lock = 1;
435
436         /* okay, njsf said /dev/dsp writing is not mt safe,
437          * also i hate OSS, so let's block everything here :) -hroptatyr
438          */
439 #if defined(HAVE_THREADS) && 0
440         pthread_mutex_lock(&mss->substream_mutex);
441 #endif
442
443         if (sound_oss_open_device(sod) < 0) {
444                 OSS_DEBUG_HW("Opening device failed.\n");
445                 sod->device_fd = -1;
446                 /* warning? */
447                 message(GETTEXT("audio-oss: "
448                                 "Opening OSS device failed."));
449                 sound_oss_close_device(sod);
450                 SXE_MUTEX_UNLOCK(&sod->mtx);
451                 return 0;
452         }
453
454         /* init the sosd */
455         sosd->paused = sosd->volume = 0;
456         sosd->samplerate = sosd->channels = 0;
457         sosd->coe_ch_cnt = 0;
458
459         if (sound_oss_init_device(sod, sosd) < 0) {
460                 OSS_DEBUG_HW("Device not configurable.\n");
461                 /* warning? */
462                 message(GETTEXT("audio-oss: "
463                                 "Cannot access OSS device."));
464                 sound_oss_close_device(sod);
465                 SXE_MUTEX_UNLOCK(&sod->mtx);
466                 return 0;
467         }
468         if(sosd->channels==0) {
469                 message(GETTEXT("audio-oss: "
470                                 "No channels."));
471                 sound_oss_close_device(sod);
472                 SXE_MUTEX_UNLOCK(&sod->mtx);
473                 return 0;
474         }
475
476
477         /* the volume effect */
478         ADD_MEDIA_SAMPLE_EFFECT(
479                 sosd->coe_chain, sosd->coe_ch_cnt,
480                 MEDIA_SAMPLE_EFFECT(sxe_mse_volume), volargs);
481         volargs->num_channels = sosd->channels;
482
483         /* the rerate effect */
484         ADD_MEDIA_SAMPLE_EFFECT(
485                 sosd->coe_chain, sosd->coe_ch_cnt,
486                 MEDIA_SAMPLE_EFFECT(sxe_mse_rerate), rrargs);
487         rrargs->num_channels = sosd->channels;
488         rrargs->srcrate = rrargs->tgtrate = 1;
489
490         OSS_DEBUG_COE("have %d coerce functions in my chain.\n",
491                       sosd->coe_ch_cnt);
492
493
494         XAUDIO_DEVICE_STATE(device) = ASTATE_ALIVE;
495         SXE_MUTEX_UNLOCK(&sod->mtx);
496
497         /* rewind the stream */
498         media_stream_meth(ms, rewind)(mss);
499
500         /* play chunks of the stream */
501         SXE_MUTEX_LOCK(&aj->mtx);
502         if (aj->buffer_alloc_size < SOUND_MAX_AUDIO_FRAME_SIZE) {
503                 alloced_myself = 1;
504                 aj->buffer = xmalloc_atomic(SOUND_MAX_AUDIO_FRAME_SIZE);
505                 aj->buffer_alloc_size = SOUND_MAX_AUDIO_FRAME_SIZE;
506         }
507         tmpbuf = (sxe_media_sample_t*)aj->buffer;
508         resolution = (sosd->mtap->samplerate * MTPSTATE_REACT_TIME) / 1000000;
509         fd = sod->device_fd;
510         natlen = 0;
511         SXE_MUTEX_UNLOCK(&aj->mtx);
512
513         while (aj->play_state != MTPSTATE_STOP) {
514
515 #ifdef EF_USE_ASYNEQ
516                 if (audio_job_queue(aj)) {
517                         sound_oss_handle_aj_events(aj);
518                 }
519 #endif
520
521                 SXE_MUTEX_LOCK(&aj->mtx);
522                 mtp = aj->play_state;
523                 SXE_MUTEX_UNLOCK(&aj->mtx);
524                 switch (mtp) {
525                 case MTPSTATE_RUN:
526                         if (natlen > 0)
527                                 goto write_buf;
528
529                         /* otherwise we simply fetch a new bunch of samples */
530                         len = media_stream_meth(ms, read)(
531                                 mss, aj->buffer, resolution);
532                         if (!len) {
533                                 OSS_DEBUG_S("finished\n");
534                                 SXE_MUTEX_LOCK(&aj->mtx);
535                                 aj->play_state = MTPSTATE_STOP;
536                                 SXE_MUTEX_UNLOCK(&aj->mtx);
537                                 break;
538                         }
539                         /* set up the volume args */
540                         volargs->volume[0] = volargs->volume[1] =
541                                 aj->volume;
542                         /* set up the rerate args */
543                         rrargs->tweak = aj->ratetrafo;
544
545                         /* coerce the stuff */
546                         tmplen = sosd->channels*len;
547                         for (i = 0; i < sosd->coe_ch_cnt; i++) {
548                                 OSS_DEBUG_COE("calling coerce "
549                                               "%d on b:0x%x l:%d\n",
550                                               i, (unsigned int)tmpbuf, tmplen);
551                                 tmplen = CALL_MEDIA_SAMPLE_EFFECT(
552                                         sosd->coe_chain, i,
553                                         tmpbuf, tmpbuf, tmplen);
554                         }
555
556                         /* bring back to S16 or U8 */
557                         MEDIA_SAMPLE_FORMAT_DOWNSAMPLE(sosd->msf)(
558                                 aj->buffer, aj->buffer, tmplen);
559
560                         /* convert tmplen back to number of frames */
561                         natlen = tmplen * sosd->framesize / sosd->channels;
562                         bptr = aj->buffer;
563
564                 write_buf:
565                         OSS_DEBUG_S("remaining cruft: %d bytes\n", natlen);
566                         if ((written = write(fd, bptr, natlen)) < 0) {
567                                 OSS_DEBUG_S("ERROR in write()\n");
568                                 natlen = 0;
569                         } else if (written) {
570                                 natlen -= written;
571                                 bptr += written;
572                         } else {
573                                 natlen = 0;
574                                 ioctl(fd, SNDCTL_DSP_SYNC, NULL);
575                         }
576                         break;
577                 case MTPSTATE_PAUSE:
578                         OSS_DEBUG("sleeping for %d\n", resolution);
579                         usleep(resolution);
580                         break;
581                 case MTPSTATE_UNKNOWN:
582                 case MTPSTATE_STOP:
583                 case NUMBER_OF_MEDIA_THREAD_PLAY_STATES:
584                 default:
585                         OSS_DEBUG("ACK, quit\n");
586                         SXE_MUTEX_LOCK(&aj->mtx);
587                         aj->play_state = MTPSTATE_STOP;
588                         SXE_MUTEX_UNLOCK(&aj->mtx);
589                         break;
590                 }
591         }
592
593         /* Now cleanup all used resources */
594         bptr = NULL;
595         SXE_MUTEX_LOCK(&aj->mtx);
596         if (alloced_myself && aj->buffer) {
597                 xfree(aj->buffer);
598         }
599         aj->buffer = NULL;
600         aj->buffer_alloc_size = 0;
601         SXE_MUTEX_UNLOCK(&aj->mtx);
602
603         sound_oss_close_device(sod);
604
605 #if defined(HAVE_THREADS) && 0
606         pthread_mutex_unlock(&mss->substream_mutex);
607 #endif
608
609         return 1;
610 }
611
612 #undef MYSELF