Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: downloads/openal-0.0.8/src/backends/alc_backend_linux.c @ 17

Last change on this file since 17 was 17, checked in by landauf, 16 years ago

added openal

File size: 13.9 KB
Line 
1/* -*- mode: C; tab-width:8; c-basic-offset:8 -*-
2 * vi:set ts=8:
3 *
4 * lin_dsp.c
5 *
6 * functions related to the aquisition and management of /dev/dsp under
7 * linux.
8 *
9 */
10#include "al_siteconfig.h"
11
12#include <AL/al.h>
13#include <AL/alext.h>
14#include <errno.h>
15#include <fcntl.h>
16#include <linux/soundcard.h>
17#include <stdio.h>
18#include <stdlib.h>
19#include <string.h>
20#include <sys/ioctl.h>
21#include <sys/mman.h>
22#include <sys/stat.h>
23#include <sys/time.h>
24#include <sys/types.h>
25#include <unistd.h>
26
27#include "al_config.h"
28#include "al_main.h"
29#include "al_debug.h"
30#include "alc/alc_context.h"
31#include "backends/alc_backend.h"
32
33#ifdef WORDS_BIGENDIAN
34#define AFMT_S16 AFMT_S16_BE
35#else
36#define AFMT_S16 AFMT_S16_LE
37#endif /* WORDS_BIGENDIAN */
38
39/*
40 * DONTCARE was (1 << 16), but that breaks 4Front's commercial drivers.
41 *  According to their reference manual, it musts be (2 << 16) or higher
42 *  or it is ignored, which is not good. Thanks to 4Front for tracking this
43 *  down.   --ryan.
44 */
45#define DONTCARE ( 8 << 16)
46
47/* convert an alc channel to a linux dsp channel */
48static int alcChannel_to_dsp_channel(ALCenum alcc);
49
50/* /dev/dsp variables */
51static fd_set dsp_fd_set;
52static int mixer_fd    = -1; /* /dev/mixer file read/write descriptor */
53static ALboolean use_select = AL_TRUE;
54
55/* gets user prefered path */
56static const char *lin_getwritepath(void);
57static const char *lin_getreadpath(void);
58static int aquire_read(void);
59static int grab_mixerfd(void);
60static int try_to_open(const char **paths, int n_paths, const char **used_path, int mode);
61
62/* set the params associated with a file descriptor */
63static int set_fd(int dsp_fd, ALboolean readable,
64                              ALuint *bufsiz,
65                              ALuint *fmt,
66                              ALuint *speed,
67                              ALuint *channels);
68
69
70/* convert the format channel from /dev/dsp to openal format */
71static int LIN2ALFMT(int fmt, int channels)
72{
73        switch(fmt)
74        {
75                case AFMT_U8:
76                        switch(channels)
77                        {
78                                case 1: return AL_FORMAT_MONO8;
79                                case 2: return AL_FORMAT_STEREO8;
80                                case 4: return AL_FORMAT_QUAD8_LOKI;
81                                default: return -1;
82                        }
83                case AFMT_S16:
84                        switch(channels)
85                        {
86                                case 1: return AL_FORMAT_MONO16;
87                                case 2: return AL_FORMAT_STEREO16;
88                                case 4: return AL_FORMAT_QUAD16_LOKI;
89                                default: return -1;
90                        }
91                default:
92#ifdef DEBUG_MAXIMUS
93                        fprintf(stderr, "unsupported dsp format\n");
94#endif
95                        return -1;
96        }
97}
98
99/* convert the format channel from openal to /dev/dsp format */
100static int AL2LINFMT(int fmt)
101{
102        switch(fmt) {
103                case AL_FORMAT_MONO16:
104                case AL_FORMAT_STEREO16:
105                case AL_FORMAT_QUAD16_LOKI:
106                        return AFMT_S16;
107                        break;
108                case AL_FORMAT_MONO8:
109                case AL_FORMAT_STEREO8:
110                case AL_FORMAT_QUAD8_LOKI:
111                        return AFMT_U8;
112                        break;
113                default:
114#ifdef DEBUG_MAXIMUS
115                  fprintf(stderr, "unknown format 0x%x\n", fmt);
116#endif
117                  break;
118        }
119
120        return -1;
121}
122
123
124/*
125 * Disable non-blocking on a file descriptor. Returns non-zero on
126 *  success, zero on failure.  --ryan.
127 */
128static int toggle_nonblock(int fd, int state)
129{
130        int retval = 0;
131        int flags = fcntl(fd, F_GETFL);
132        if (flags != -1) {
133                if (state) {
134                        flags |= O_NONBLOCK;
135                } else {
136                        flags &= ~O_NONBLOCK;
137                }
138
139                if(fcntl(fd, F_SETFL, flags) != -1) {
140                        retval = 1;
141                }
142        }
143
144        if (!retval) {
145                perror("fcntl");
146        }
147
148        return(retval);
149}
150
151
152/*
153 *
154 *  Format of divisor is bit field where:
155 *
156 *
157 *         MSHW          LSHW
158 *  [ some big value |     x  ]
159 *
160 *  where x is translated into 2^x, and used as the
161 *  dma buffer size.
162 *
163 */
164static void *grab_write_native(void)
165{
166        static int write_fd;
167        Rcvar rc_use_select;
168        const char *writepath = NULL;
169        int divisor = DONTCARE | _alSpot( _ALC_DEF_BUFSIZ );
170        const char *tried_paths[] = {
171                "",
172                "/dev/sound/dsp",
173                "/dev/dsp"
174        };
175        tried_paths[0] = lin_getwritepath();
176        write_fd = try_to_open(tried_paths, 3, &writepath, O_WRONLY | O_NONBLOCK);
177        if(write_fd < 0) {
178                perror("open /dev/[sound/]dsp");
179                return NULL;
180        }
181
182        if(ioctl(write_fd, SNDCTL_DSP_SETFRAGMENT, &divisor) < 0) {
183                perror("ioctl SETFRAGMENT grab");
184        }
185
186        toggle_nonblock(write_fd, 0);
187
188        /* now get mixer_fd */
189        mixer_fd = grab_mixerfd();
190
191        /*
192         * Some drivers, ie aurreal ones, don't implemented select.
193         * I like to use select, as it ensures that we don't hang on
194         * a bum write forever, but if you're in the unadmirable position
195         * of having one of these cards I'm sure it's a small comfort.
196         *
197         * So, we have a special alrc var: native-use-select, that, if
198         * set to #f, causes us to not do a select on the fd before
199         * writing to it.
200         */
201        use_select = AL_TRUE;
202
203        rc_use_select = rc_lookup("native-use-select");
204        if(rc_use_select != NULL) {
205                if(rc_type(rc_use_select) == ALRC_BOOL) {
206                        use_select = rc_tobool(rc_use_select);
207                }
208        }
209
210#ifdef DEBUG
211        fprintf(stderr, "grab_native: (path %s fd %d)\n", writepath, write_fd);
212#endif
213
214        return &write_fd;
215}
216
217/*
218 *
219 *  Format of divisor is bit field where:
220 *
221 *
222 *         MSHW          LSHW
223 *  [ some big value |     x  ]
224 *
225 *  where x is translated into 2^x, and used as the
226 *  dma buffer size.
227 *
228 */
229static void *grab_read_native(void)
230{
231        static int read_fd;
232
233        read_fd = aquire_read();
234        if( read_fd < 0) {
235                return NULL;
236        }
237
238        return &read_fd;
239}
240
241void *
242alcBackendOpenNative_( ALC_OpenMode mode )
243{
244        return mode == ALC_OPEN_INPUT_ ? grab_read_native() : grab_write_native();
245}
246
247static int grab_mixerfd(void) {
248        const char *tried_paths[] = {
249                "/dev/sound/mixer",
250                "/dev/mixer"
251        };
252        mixer_fd = try_to_open(tried_paths, 2, NULL, O_WRONLY | O_NONBLOCK);
253
254        if(mixer_fd > 0) {
255                toggle_nonblock(mixer_fd, 0);
256                return mixer_fd;
257        } else {
258                perror("open /dev/[sound/]mixer");
259        }
260
261        return -1;
262}
263
264void release_native(void *handle) {
265        int handle_fd;
266
267        if(handle == NULL) {
268                return;
269        }
270
271        handle_fd = *(int *) handle;
272
273        if(ioctl(handle_fd, SNDCTL_DSP_RESET) < 0) {
274#ifdef DEBUG
275                fprintf(stderr, "Couldn't reset dsp\n");
276#endif
277        }
278
279        ioctl(handle_fd, SNDCTL_DSP_SYNC, NULL);
280        if((close(handle_fd) < 0) || (close(mixer_fd) < 0)) {
281                return;
282        }
283
284        *(int *) handle = -1;
285        mixer_fd        = -1;
286
287        return;
288}
289
290ALfloat
291get_nativechannel(UNUSED(void *handle), ALuint channel)
292{
293        int request = alcChannel_to_dsp_channel(channel);
294        int retval;
295        if(ioctl(mixer_fd, MIXER_READ(request), &retval) < 0) {
296                return -1;
297        }
298        return (retval >> 8) / 100.0;
299}
300
301
302/*
303 * Okay:
304 *
305 * Set audio channel expects an integer, in the range of
306 * 0 - 100.  But wait!  It expects the integer to be
307 * partitioned into a 16bit empty, L/R channel pair (high bits left,
308 * low bits right), each 8 bit pair in the range 0 - 100.
309 *
310 * Kludgey, and obviously not the right way to do this
311 */
312int set_nativechannel(UNUSED(void *handle), ALuint channel, ALfloat volume) {
313        int request = alcChannel_to_dsp_channel(channel);
314        int unnormalizedvolume;
315
316        unnormalizedvolume = volume * 100;
317        unnormalizedvolume <<= 8;
318        unnormalizedvolume += (volume * 100);
319
320        if(ioctl(mixer_fd, MIXER_WRITE(request), &unnormalizedvolume) < 0) {
321                return -1;
322        }
323
324        return 0;
325}
326
327/* convert the mixer channel from ALC to /dev/mixer format */
328static int alcChannel_to_dsp_channel(ALCenum alcc) {
329        switch(alcc) {
330                case ALC_CHAN_MAIN_LOKI: return SOUND_MIXER_VOLUME;
331                case ALC_CHAN_CD_LOKI:   return SOUND_MIXER_CD;
332                case ALC_CHAN_PCM_LOKI:  return SOUND_MIXER_PCM;
333                default: return -1;
334        }
335}
336
337void pause_nativedevice(void *handle) {
338        int fd;
339
340        if(handle == NULL) {
341                return;
342        }
343
344        fd = *(int *) handle;
345
346        toggle_nonblock(fd, 1);
347
348#if 0
349        if(ioctl(fd, SNDCTL_DSP_POST, 0) == -1) {
350                perror("ioctl");
351        }
352#endif
353
354        return;
355}
356
357void resume_nativedevice(void *handle) {
358        int fd;
359
360        if(handle == NULL) {
361                return;
362        }
363
364        fd = *(int *) handle;
365
366        toggle_nonblock(fd, 0);
367
368/*
369        if(ioctl(fd, SNDCTL_DSP_SYNC, 0) == -1) {
370                perror("ioctl");
371        }
372 */
373
374        return;
375}
376
377static const char *lin_getwritepath(void) {
378        Rcvar devdsp_path = rc_lookup("lin-dsp-path");
379        static char retval[65]; /* FIXME */
380
381        if(devdsp_path == NULL) {
382                return NULL;
383        }
384
385        switch(rc_type(devdsp_path)) {
386                case ALRC_STRING:
387                        rc_tostr0(devdsp_path, retval, 64);
388                        break;
389                default:
390                        return NULL;
391        }
392
393        return retval;
394}
395
396static const char *lin_getreadpath(void) {
397        Rcvar devdsp_path = rc_lookup("lin-dsp-read-path");
398        static char retval[65]; /* FIXME */
399
400        if(devdsp_path == NULL) {
401                /*
402                 * no explicit read path?  try the default
403                 * path.
404                 */
405                devdsp_path = rc_lookup("lin-dsp-path");
406        }
407
408        if(devdsp_path == NULL) {
409                return NULL;
410        }
411
412        switch(rc_type(devdsp_path)) {
413                case ALRC_STRING:
414                        rc_tostr0(devdsp_path, retval, 64);
415                        break;
416                default:
417                        return NULL;
418        }
419
420
421        /*
422         * stupid.  /dev/dsp1 cannot be used for reading,
423         * only /dev/dsp
424         */
425        if(retval[strlen(retval) - 1] == '1') {
426                retval[strlen(retval) - 1] = '\0';
427        }
428
429        return retval;
430}
431
432/* capture data from the audio device */
433ALsizei capture_nativedevice(void *handle,
434                          void *capture_buffer,
435                          int bufsiz) {
436        int read_fd = *(int *)handle;
437        int retval;
438
439        retval = read(read_fd, capture_buffer, bufsiz);
440        return retval > 0 ? retval : 0;
441}
442
443static int aquire_read(void) {
444        int read_fd;
445        const char *readpath = NULL;
446        int divisor = _alSpot(_ALC_DEF_BUFSIZ) | (1<<16);
447        const char *tried_paths[] = {
448                "",
449                "/dev/sound/dsp",
450                "/dev/dsp"
451        };
452        tried_paths[0] = lin_getreadpath();
453        read_fd = try_to_open(tried_paths, 3, &readpath, O_RDONLY | O_NONBLOCK);
454        if(read_fd >= 0) {
455#if 0 /* Reads should be non-blocking */
456                toggle_nonblock(read_fd, 0);
457#endif
458                if(ioctl(read_fd, SNDCTL_DSP_SETFRAGMENT, &divisor) < 0) {
459                        perror("ioctl SETFRAGMENT");
460                }
461        }
462
463        return read_fd;
464}
465
466static ALboolean set_write_native(UNUSED(void *handle),
467                                  ALuint *bufsiz,
468                                  ALenum *fmt,
469                                  ALuint *speed) {
470        int write_fd = *(int *)handle;
471        ALuint linformat;
472        ALuint channels = _alGetChannelsFromFormat(*fmt);
473        int err;
474
475        if(write_fd < 0) {
476                return AL_FALSE;
477        }
478
479        linformat = AL2LINFMT(*fmt);
480
481        err = set_fd(write_fd, AL_FALSE, bufsiz, &linformat, speed, &channels);
482        if(err < 0) {
483#ifdef DEBUG
484                fprintf(stderr, "Could not do write_fd\n");
485#endif
486                return AL_FALSE;
487        }
488
489        /* set format for caller */
490        *fmt = LIN2ALFMT(linformat, channels);
491
492        return AL_TRUE;
493}
494
495static ALboolean set_read_native(UNUSED(void *handle),
496                                 ALuint *bufsiz,
497                                 ALenum *fmt,
498                                 ALuint *speed) {
499        int read_fd = *(int *)handle;
500        ALuint linformat;
501        ALuint channels = 1;
502
503        linformat = AL2LINFMT(*fmt);
504
505        if(set_fd(read_fd, AL_TRUE, bufsiz, &linformat, speed, &channels) >= 0) {
506                /* set format for caller */
507                *fmt = LIN2ALFMT(linformat, channels);
508
509                return AL_TRUE;
510        }
511
512        return AL_FALSE;
513}
514
515ALboolean
516alcBackendSetAttributesNative_(ALC_OpenMode mode, void *handle, ALuint *bufsiz, ALenum *fmt, ALuint *speed)
517{
518        return mode == ALC_OPEN_INPUT_ ?
519                set_read_native(handle, bufsiz, fmt, speed) :
520                set_write_native(handle, bufsiz, fmt, speed);
521}
522
523void native_blitbuffer(void *handle, void *dataptr, int bytes_to_write) {
524        struct timeval tv = { 0, 800000 }; /* at most .8 secs */
525        int iterator = 0;
526        int err;
527        int fd;
528
529        if(handle == NULL) {
530                return;
531        }
532
533        fd = *(int *) handle;
534
535        assert( fd >= 0 );
536
537        for(iterator = bytes_to_write; iterator > 0; ) {
538                FD_ZERO(&dsp_fd_set);
539                FD_SET(fd, &dsp_fd_set);
540
541                if(use_select == AL_TRUE) {
542                        err = select(fd + 1, NULL, &dsp_fd_set, NULL, &tv);
543                        if(FD_ISSET(fd, &dsp_fd_set) == 0) {
544                                /*
545                                 * error or timeout occured, don't try
546                                 * and write.
547                                 */
548                                fprintf(stderr,
549                                "native_blitbuffer: select error occured\n");
550                                return;
551                        }
552                }
553
554                assert(iterator > 0);
555                assert(iterator <= bytes_to_write);
556
557                err = write(fd,
558                            (char *) dataptr + bytes_to_write - iterator,
559                            iterator);
560
561                if(err < 0) {
562#ifdef DEBUG_MAXIMUS
563                        fprintf( stderr, "write error: ( fd %d error %s )\n",
564                                fd, strerror(err));
565#endif
566                        assert( 0 );
567                        return;
568                }
569
570                iterator -= err;
571        };
572
573        return;
574}
575
576static int set_fd(int dsp_fd, ALboolean readable,
577                     ALuint *bufsiz,
578                     ALuint *fmt,
579                     ALuint *speed,
580                     ALuint *channels)
581{
582        struct audio_buf_info info;
583
584        if(dsp_fd < 0) {
585                return -1;
586        }
587
588#ifdef DEBUG
589        fprintf( stderr, "set_fd in: bufsiz %d fmt 0x%x speed %d channels %d\n",
590                 *bufsiz, *fmt, *speed, *channels );
591#endif
592
593
594#if 0 /* This code breaks 4Front's commercial drivers. Just say no. --ryan. */
595{
596        int divisor = DONTCARE | _alSpot( *bufsiz );
597        if( ioctl(dsp_fd, SNDCTL_DSP_SETFRAGMENT, &divisor ) < 0) {
598#ifdef DEBUG
599                perror("ioctl SETFRAGMENT");
600#endif
601        }
602}
603#endif
604
605
606        /* reset card defaults */
607        if(ioctl(dsp_fd, SNDCTL_DSP_RESET, NULL) < 0) {
608#ifdef DEBUG
609                perror("set_devsp reset ioctl");
610#endif
611                return AL_FALSE;
612        }
613
614        if(ioctl(dsp_fd, SNDCTL_DSP_SETFMT, fmt) < 0) {
615#ifdef DEBUG
616                fprintf(stderr, "fmt %d\n", *fmt);
617                perror("set_devsp format ioctl");
618#endif
619                return AL_FALSE;
620        }
621
622        if(ioctl(dsp_fd, SNDCTL_DSP_CHANNELS, channels)) {
623#ifdef DEBUG
624                fprintf(stderr, "channels %d\n", *channels);
625                perror("set_devsp channels ioctl");
626#endif
627                return AL_FALSE;
628        }
629
630        if( readable == AL_TRUE ) {
631                /*
632                 * This is for reading.  Don't really use
633                 * the speed argument.
634                 */
635                *speed = 16000;
636
637                /* Try to set the speed (ignore value), then read it back */
638                ioctl(dsp_fd, SNDCTL_DSP_SPEED, speed);
639                if (ioctl(dsp_fd, SOUND_PCM_READ_RATE, speed) < 0) {
640#ifdef DEBUG
641                        char errbuf[256];
642
643                        snprintf(errbuf, sizeof(errbuf), "(fd %d speed %d)", dsp_fd, *speed);
644
645                        perror(errbuf);
646                        return AL_FALSE;
647#endif
648                }
649/*printf("Set recording rate: %d\n", *speed);*/
650
651        } else {
652                /* writable, set speed, otherwise no */
653                if(ioctl(dsp_fd, SNDCTL_DSP_SPEED, speed) < 0) {
654#ifdef DEBUG
655                        char errbuf[256];
656
657                        snprintf(errbuf, sizeof(errbuf), "(fd %d speed %d)", dsp_fd, *speed);
658
659                        perror(errbuf);
660                        return AL_FALSE;
661#endif
662                }
663
664                if(ioctl(dsp_fd, SNDCTL_DSP_GETOSPACE, &info) < 0) {
665#ifdef DEBUG
666                        perror("ioctl SNDCTL_DSP_GETOSPACE");
667#endif
668                }
669
670                /* set bufsiz correctly */
671
672                *bufsiz = info.fragsize;
673
674#ifdef DEBUG
675                if( *bufsiz & (*bufsiz - 1) ) {
676                        fprintf( stderr, "Non power-of-two bufsiz %d\n",
677                                *bufsiz );
678                }
679#endif
680        }
681
682#ifdef DEBUG
683        fprintf( stderr, "set_fd out: bufsiz %d fmt 0x%x speed %d channels %d\n",
684                 *bufsiz, *fmt, *speed, *channels );
685#endif
686
687        return 0;
688}
689
690int try_to_open(const char **paths, int n_paths, const char **used_path, int mode)
691{
692        int i, fd = -1;
693        for(i = 0; i != n_paths; ++i)
694        {
695                if(paths[i])
696                {
697                        if(used_path) *used_path = paths[i];
698                        fd = open(paths[i], mode);
699                        if(fd >= 0) return fd;
700                }
701        }
702        return fd;
703}
704
Note: See TracBrowser for help on using the repository browser.