/* * Copyright (c) 2010 Jacob Meuser * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "allegro.h" #if (defined ALLEGRO_WITH_SNDIODIGI) && ((!defined ALLEGRO_WITH_MODULES) || (defined ALLEGRO_MODULE)) #include "allegro/internal/aintern.h" #include "allegro/platform/aintunix.h" #include #include #include #include #include #include static int sndio_detect(int input); static int sndio_init(int input, int voices); static void sndio_exit(int input); static int sndio_set_mixer_volume(int volume); static int sndio_get_mixer_volume(void); static int sndio_buffer_size(void); static int sndio_rec_cap_rate(int bits, int stereo); static int sndio_rec_cap_parm(int rate, int bits, int stereo); static int sndio_rec_source(int source); static int sndio_rec_start(int rate, int bits, int stereo); static void sndio_rec_stop(void); static int sndio_rec_read(void *buf); static int open_sndio_device(int input); static void sndio_update(int threaded); static void movecb(void *addr, int delta); static void volcb(void *addr, unsigned vol); static struct sio_hdl *hdl; static struct sio_par par; static int sndio_signed; static int sndio_play_bufsize, sndio_play_round, sndio_play_appbufsz; static unsigned char *sndio_play_bufdata; static char sndio_desc[256] = EMPTY_STRING; long long sndio_realpos, sndio_playpos; int sndio_volume; static int sndio_save_bits, sndio_save_stereo, sndio_save_freq; static int sndio_rec_bufsize, sndio_rec_round, sndio_rec_appbufsz; static unsigned char *sndio_rec_bufdata; DIGI_DRIVER digi_sndio = { DIGI_SNDIO, empty_string, empty_string, "sndio", 0, 0, MIXER_MAX_SFX, MIXER_DEF_SFX, /* setup routines */ sndio_detect, sndio_init, sndio_exit, sndio_set_mixer_volume, sndio_get_mixer_volume, /* for use by the audiostream functions */ NULL, NULL, sndio_buffer_size, /* voice control functions */ _mixer_init_voice, _mixer_release_voice, _mixer_start_voice, _mixer_stop_voice, _mixer_loop_voice, /* position control functions */ _mixer_get_position, _mixer_set_position, /* volume control functions */ _mixer_get_volume, _mixer_set_volume, _mixer_ramp_volume, _mixer_stop_volume_ramp, /* pitch control functions */ _mixer_get_frequency, _mixer_set_frequency, _mixer_sweep_frequency, _mixer_stop_frequency_sweep, /* pan control functions */ _mixer_get_pan, _mixer_set_pan, _mixer_sweep_pan, _mixer_stop_pan_sweep, /* effect control functions */ _mixer_set_echo, _mixer_set_tremolo, _mixer_set_vibrato, /* input functions */ 0, 0, sndio_rec_cap_rate, sndio_rec_cap_parm, sndio_rec_source, sndio_rec_start, sndio_rec_stop, sndio_rec_read }; /* used to probe and to configure the device. don't use sio_start() here. */ static int open_sndio_device(int input) { hdl = sio_open(NULL, (input ? SIO_REC : SIO_PLAY), 0); if (hdl == NULL) { uszprintf(allegro_error, ALLEGRO_ERROR_SIZE, get_config_text("sio_opn failed")); return -1; } sio_initpar(&par); par.bits = (_sound_bits == 8) ? 8 : 16; par.sig = (_sound_bits == 8) ? 0 : 1; if (input) par.rchan = (_sound_stereo) ? 2 : 1; else par.pchan = (_sound_stereo) ? 2 : 1; par.rate = (_sound_freq > 0) ? _sound_freq : 48000; par.le = SIO_LE_NATIVE; /* allegro wants small blocks */ par.round = 512; par.appbufsz = par.rate / 10; if (!sio_setpar(hdl, &par) || !sio_getpar(hdl, &par) || (par.bits != 8 && par.bits != 16) || (par.bits == 8 && par.sig) || (par.bits == 16 && !par.sig) || (par.bits == 16 && par.le != SIO_LE_NATIVE) || (input && (par.rchan != 1 && par.rchan != 2)) || (!input && (par.pchan != 1 && par.pchan != 2))) { ustrzcpy(allegro_error, ALLEGRO_ERROR_SIZE, get_config_text("could not set sndio parameters")); sio_close(hdl); return -1; } _sound_bits = par.bits; _sound_stereo = input ? par.rchan == 2 : par.pchan == 2; _sound_freq = par.rate; if (input) { sndio_rec_round = par.round; sndio_rec_appbufsz = par.appbufsz; sndio_rec_bufsize = par.round * par.bps * par.rchan; } else { sndio_play_round = par.round; sndio_play_appbufsz = par.appbufsz; sndio_play_bufsize = sndio_play_round * par.bps * par.pchan; } sndio_signed = par.sig ? 1 : 0; return 0; } static int sndio_detect(int input) { if (input) { if (digi_driver != digi_input_driver) { ustrzcpy(allegro_error, ALLEGRO_ERROR_SIZE, get_config_text("sndio output driver must be installed before input can be read")); return FALSE; } return TRUE; } if (open_sndio_device(0) != 0) return FALSE; sio_close(hdl); return TRUE; } /* number of samples per channel in a block */ static int sndio_buffer_size(void) { return sndio_play_round; } /* callback for data movement notification */ static void movecb(void *addr, int delta) { sndio_realpos += delta; } /* callback for volume change notification */ static void volcb(void *addr, unsigned vol) { sndio_volume = vol; } /* write as many blocks as is currently possible */ static void sndio_update(int threaded) { struct pollfd pfd; nfds_t nfds; int i, nblocks, nbytes; /* make sure counters have been updated */ nfds = sio_pollfd(hdl, &pfd, POLLOUT); poll(&pfd, nfds, 0); if (!(sio_revents(hdl, &pfd) & POLLOUT)) return; nblocks = (sndio_play_appbufsz - (sndio_playpos - sndio_realpos)) / sndio_play_round; /* we got POLLOUT, so we can write something. if we don't * write anything, we could underrun. */ if (nblocks < 1) nblocks = 1; for (i = 0; i < nblocks; i++) { sio_write(hdl, sndio_play_bufdata, sndio_play_bufsize); sndio_playpos += sndio_play_round; if (sio_eof(hdl)) { /* print error message? */ return; } _mix_some_samples((uintptr_t) sndio_play_bufdata, 0, sndio_signed); } } static int sndio_init(int input, int voices) { char tmp1[128], tmp2[128]; if (input) { digi_driver->rec_cap_bits = 16; digi_driver->rec_cap_stereo = TRUE; return 0; } if (open_sndio_device(0) != 0) return -1; sndio_play_bufdata = _AL_MALLOC_ATOMIC(sndio_play_bufsize); if (sndio_play_bufdata == 0) { ustrzcpy(allegro_error, ALLEGRO_ERROR_SIZE, get_config_text("Can not allocate audio buffer")); sio_close(hdl); return -1; } sndio_realpos = sndio_playpos = 0; sio_onmove(hdl, movecb, NULL); sndio_volume = 127; sio_onvol(hdl, volcb, NULL); if (!sio_start(hdl)) { ustrzcpy(allegro_error, ALLEGRO_ERROR_SIZE, get_config_text("Can not start sndio")); sio_close(hdl); return -1; } digi_sndio.voices = voices; /* first arg is total number of samples */ if (_mixer_init(sndio_play_round * (_sound_stereo ? 2 : 1), _sound_freq, _sound_stereo, ((_sound_bits == 16) ? 1 : 0), &digi_sndio.voices) != 0) { ustrzcpy(allegro_error, ALLEGRO_ERROR_SIZE, get_config_text("Can not init software mixer")); sio_close(hdl); return -1; } _mix_some_samples((uintptr_t) sndio_play_bufdata, 0, sndio_signed); /* Add audio interrupt. */ _unix_bg_man->register_func(sndio_update); uszprintf(sndio_desc, sizeof(sndio_desc), get_config_text("%s: %d bits, %s, %d Hz, %s"), "sndio device", _sound_bits, uconvert_ascii((sndio_signed ? "signed" : "unsigned"), tmp1), _sound_freq, uconvert_ascii((par.pchan == 2 ? "stereo" : "mono"), tmp2)); digi_driver->desc = sndio_desc; return 0; } static void sndio_exit(int input) { if (input) return; _unix_bg_man->unregister_func(sndio_update); _AL_FREE(sndio_play_bufdata); sndio_play_bufdata = 0; _mixer_exit(); if (hdl != NULL) sio_close(hdl); hdl = NULL; } /* 'volume' is 0-255 */ static int sndio_set_mixer_volume(int volume) { if (!sio_setvol(hdl, volume / 2)) return -1; return 0; } /* should return 0-255 */ static int sndio_get_mixer_volume(void) { return sndio_volume * 2; } /* Returns maximum recording sampling rate. */ static int sndio_rec_cap_rate(int bits, int stereo) { /* should use sio_getcap() */ return 48000; } /* Returns whether the specified parameters can be set. */ static int sndio_rec_cap_parm(int rate, int bits, int stereo) { /* should use sio_getcap() */ return 1; } /* Sets the sampling source for audio recording. */ static int sndio_rec_source(int source) { /* not implemented in sndio */ switch (source) { case SOUND_INPUT_MIC: break; case SOUND_INPUT_LINE: break; case SOUND_INPUT_CD: break; default: return -1; } return 0; } /* * Re-opens device with read-mode and starts recording (half-duplex). * Returns the DMA buffer size if successful. */ static int sndio_rec_start(int rate, int bits, int stereo) { sndio_save_bits = _sound_bits; sndio_save_stereo = _sound_stereo; sndio_save_freq = _sound_freq; _unix_bg_man->unregister_func(sndio_update); if (hdl != NULL) sio_close(hdl); hdl = NULL; _sound_bits = bits; _sound_stereo = stereo; _sound_freq = rate; if (open_sndio_device(1) != 0) return 0; sndio_volume = 127; sio_onvol(hdl, volcb, NULL); if (!sio_start(hdl)) { ustrzcpy(allegro_error, ALLEGRO_ERROR_SIZE, get_config_text("Can not start sndio for recording")); sio_close(hdl); return 0; } return sndio_rec_bufsize; } /* Stops recording and switches the device back to the original mode. */ static void sndio_rec_stop(void) { if (hdl != NULL) sio_close(hdl); hdl = NULL; _sound_bits = sndio_save_bits; _sound_stereo = sndio_save_stereo; _sound_freq = sndio_save_freq; if (open_sndio_device(0) != 0) return; sndio_realpos = sndio_playpos = 0; sio_onmove(hdl, movecb, NULL); sndio_volume = 127; sio_onvol(hdl, volcb, NULL); if (!sio_start(hdl)) { ustrzcpy(allegro_error, ALLEGRO_ERROR_SIZE, get_config_text("Can not start sndio")); sio_close(hdl); return; } _unix_bg_man->register_func(sndio_update); } /* Retrieves the just recorded buffer, if there is one. */ static int sndio_rec_read(void *buf) { struct pollfd pfd; nfds_t nfds; int ret, nbytes, offset = 0; /* make sure counters have been updated */ nfds = sio_pollfd(hdl, &pfd, POLLIN); poll(&pfd, nfds, 0); sio_revents(hdl, &pfd); if (!(sio_revents(hdl, &pfd) & POLLIN)) return 0; nbytes = sndio_rec_bufsize; while (nbytes) { ret = sio_read(hdl, buf + offset, nbytes); if (sio_eof(hdl)) return 0; offset += ret; nbytes -= ret; } return 1; } #endif /* ALLEGRO_WITH_SNDIODIGI */