#include #include #include #define ALSA_PCM_NEW_HW_PARAMS_API #define ALSA_PCM_NEW_SW_PARAMS_API #include /* Nested dynamic loaded extension magic ... */ #include "../../Data/Audio.m" AudioVtab *AudioVptr; #define SAMP_RATE 11025 #if 0 const char *pcm_name = "plughw:0,0"; #else const char *pcm_name = "default"; #endif static const char * audio_statestr(snd_pcm_state_t state) { switch(state) { case SND_PCM_STATE_OPEN: return "open"; case SND_PCM_STATE_SETUP: return "setup"; case SND_PCM_STATE_PREPARED: return "prepared"; case SND_PCM_STATE_RUNNING: return "running"; case SND_PCM_STATE_XRUN: return "xrun"; case SND_PCM_STATE_DRAINING: return "draining"; case SND_PCM_STATE_PAUSED: return "paused"; case SND_PCM_STATE_SUSPENDED:return "suspended"; default: return "unknown"; } } typedef struct { unsigned int samp_rate; snd_pcm_t *pcm; snd_pcm_hw_params_t *hwparams; float gain; snd_pcm_uframes_t chunk; } play_audio_t; static int audio_prepare(play_audio_t *dev) { if (dev) { int err; int dir = 0; unsigned int rate = dev->samp_rate; snd_pcm_state_t state = snd_pcm_state(dev->pcm); #if 0 warn("%s with state %s",__FUNCTION__,audio_statestr(state)); #endif /* ALSA lib is fussy - won't let you reset this struct even if setting to same value so we need to re-get the uncommitted values every time */ if ((err = snd_pcm_hw_params_any(dev->pcm,dev->hwparams)) < 0) { warn("Cannot read hwparams:%s",snd_strerror(err)); } if ((err = snd_pcm_hw_params_set_access(dev->pcm, dev->hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { warn("Cannot set access %s:%s",pcm_name,snd_strerror(err)); return 0; } /* Set sample format */ if ((err=snd_pcm_hw_params_set_format(dev->pcm, dev->hwparams, SND_PCM_FORMAT_S16)) < 0) { warn("Error setting format %s:%s",pcm_name,snd_strerror(err)); return(0); } #ifdef ALSA_PCM_NEW_HW_PARAMS_API err = snd_pcm_hw_params_set_rate_near(dev->pcm, dev->hwparams, &rate, &dir); #else rate = snd_pcm_hw_params_set_rate_near(dev->pcm, dev->hwparams, dev->samp_rate, &dir); #endif if (dir || rate != dev->samp_rate) { unsigned int num; unsigned int den; if ((err = snd_pcm_hw_params_get_rate_numden(dev->hwparams,&num,&den)) < 0) { warn("Cannot get exact rate (%s) using %d", snd_strerror(err), rate); } else { warn("Wanted %ldHz, got(%d) %ld (%u/%u=%.10gHz",dev->samp_rate,dir, rate,num,den,1.0*num/den); } dev->samp_rate = rate; } if ((err=snd_pcm_hw_params_set_channels(dev->pcm, dev->hwparams, 1)) < 0) { warn("Error setting channels %s:%s",pcm_name,snd_strerror(err)); return(0); } /* Apply HW parameter settings to */ /* PCM device and prepare device */ if ((err=snd_pcm_hw_params(dev->pcm, dev->hwparams)) < 0) { warn("Error setting parameters %s:%s",pcm_name,snd_strerror(err)); return(0); } #ifdef ALSA_PCM_NEW_HW_PARAMS_API err = snd_pcm_hw_params_get_buffer_size (dev->hwparams, &dev->chunk); #else dev->chunk = snd_pcm_hw_params_get_buffer_size (dev->hwparams); #endif state = snd_pcm_state(dev->pcm); #if 0 warn("prepared now state %s",audio_statestr(state)); #endif return 1; } return 0; } static int audio_init(play_audio_t *dev,int wait) { int err; if (!dev->gain) dev->gain = 1.0f; if (!dev->samp_rate) dev->samp_rate = SAMP_RATE; if ((err = snd_pcm_open(&dev->pcm,pcm_name,SND_PCM_STREAM_PLAYBACK,0)) < 0) { warn("Cannot open %s (%d):%s",pcm_name,wait,snd_strerror(err)); return 0; } else { if ((err = snd_pcm_hw_params_malloc(&dev->hwparams)) < 0) { warn("Cannot allocate hwparams:%s",snd_strerror(err)); } if ((err = snd_pcm_hw_params_any(dev->pcm,dev->hwparams)) < 0) { warn("Cannot read hwparams:%s",snd_strerror(err)); } return 1; } } void audio_flush(play_audio_t *dev) { if (dev->pcm) { snd_pcm_state_t state = snd_pcm_state(dev->pcm); switch(state) { case SND_PCM_STATE_RUNNING: { /* Stop PCM device after pending frames have been played */ int err = snd_pcm_drain(dev->pcm); if (err < 0) { warn(snd_strerror(err)); } break; } default: warn("%s with state %s",__FUNCTION__,audio_statestr(state)); break; } } } static void audio_close(play_audio_t *dev) { if (dev) { /* Close audio system */ if (dev->hwparams) { snd_pcm_hw_params_free(dev->hwparams); dev->hwparams = 0; } if (dev->pcm) { snd_pcm_close(dev->pcm); dev->pcm = 0; } dev->chunk = 0; } } UV audio_rate(play_audio_t *dev, UV rate) {unsigned int old = dev->samp_rate; if (rate && rate != dev->samp_rate) { snd_pcm_state_t state; int dir = 0; int err; audio_flush(dev); switch ((state = snd_pcm_state(dev->pcm))) { case SND_PCM_STATE_OPEN: break; case SND_PCM_STATE_SETUP: case SND_PCM_STATE_PREPARED: case SND_PCM_STATE_RUNNING: case SND_PCM_STATE_XRUN: case SND_PCM_STATE_DRAINING: case SND_PCM_STATE_PAUSED: case SND_PCM_STATE_SUSPENDED: default: audio_close(dev); if (!audio_init(dev,1)) { croak("Cannot re-open %s"); } break; } #if 0 warn("%s with state %s",__FUNCTION__,audio_statestr(state)); #endif #ifdef ALSA_PCM_NEW_HW_PARAMS_API dev->samp_rate = rate; err = snd_pcm_hw_params_set_rate_near(dev->pcm, dev->hwparams, &dev->samp_rate, &dir); #else dev->samp_rate = snd_pcm_hw_params_set_rate_near(dev->pcm, dev->hwparams, rate, &dir); #endif if (dir || rate != dev->samp_rate) { unsigned int num; unsigned int den; if ((err = snd_pcm_hw_params_get_rate_numden(dev->hwparams,&num,&den)) < 0) { warn("Cannot get exact rate (%s) using %d", snd_strerror(err), dev->samp_rate); } else { warn("Wanted %ldHz, got(%d) %ld (%u/%u=%.10gHz",rate,dir, dev->samp_rate,num,den,1.0*num/den); } } } return old; } void audio_DESTROY(play_audio_t *dev) { audio_flush(dev); audio_close(dev); } void audio_play16(play_audio_t *dev,int n, short *data) { if (n > 0 && dev->pcm) { snd_pcm_sframes_t ret; while (n > 0) { size_t amount = ((size_t) n > dev->chunk) ? dev->chunk : (size_t) n; while ((ret = snd_pcm_writei(dev->pcm, data, amount)) < 0) { warn("%s:%s",pcm_name,snd_strerror(ret)); snd_pcm_prepare(dev->pcm); } n -= ret; data += ret; } } } float audio_gain(play_audio_t *dev,float gain) { float prev_gain = dev->gain; if (gain >= 0.0) { if (gain != 1.0) warn("Cannot change audio gain yet"); } return prev_gain; } /* API level Play function - volume may go from the interface - it is un-natural - convert to 'short' should be done at Audio::Play level - likewise rate-matching needs to be higher level */ void audio_play(play_audio_t *dev, Audio *au, float volume) { STRLEN samp = Audio_samples(au); SV *tmp = Audio_shorts(au); if (volume >= 0) audio_gain(dev, volume); if (au->rate != audio_rate(dev,0)) audio_rate(dev, au->rate); /* Or re-sample to dev's rate ??? */ if (!dev->chunk) audio_prepare(dev); audio_play16(dev, samp, (short *) SvPVX(tmp)); SvREFCNT_dec(tmp); } MODULE = Audio::Play::#OSNAME# PACKAGE=Audio::Play::#OSNAME# PREFIX = audio_ PROTOTYPES: DISABLE play_audio_t * audio_new(class,wait = 1) char * class IV wait CODE: {static play_audio_t buf; if (!audio_init(RETVAL = &buf,wait)) { XSRETURN_NO; } } OUTPUT: RETVAL void audio_DESTROY(dev) play_audio_t * dev void audio_flush(dev) play_audio_t * dev double audio_gain(dev,val = -1.0) play_audio_t * dev double val IV audio_rate(dev,rate = 0) play_audio_t * dev IV rate void audio_play(dev, au, vol = -1.0) play_audio_t * dev Audio * au; float vol BOOT: { /* Nested dynamic loaded extension magic ... */ AudioVptr = (AudioVtab *) SvIV(perl_get_sv("Audio::Data::AudioVtab",5)); }