#include <EXTERN.h>
#include <perl.h>
#include <XSUB.h>
#define ALSA_PCM_NEW_HW_PARAMS_API
#define ALSA_PCM_NEW_SW_PARAMS_API
#include <alsa/asoundlib.h>
/* 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));
}