From 9c0e19a3be2288db79e2502e5fa450c3e20a668d Mon Sep 17 00:00:00 2001 From: Guenter Geiger Date: Fri, 9 May 2003 16:04:00 +0000 Subject: This commit was generated by cvs2svn to compensate for changes in r610, which included commits to RCS files with non-trunk default branches. svn path=/trunk/; revision=611 --- pd/src/s_audio_alsa.c | 630 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 630 insertions(+) create mode 100644 pd/src/s_audio_alsa.c (limited to 'pd/src/s_audio_alsa.c') diff --git a/pd/src/s_audio_alsa.c b/pd/src/s_audio_alsa.c new file mode 100644 index 00000000..0fa3d791 --- /dev/null +++ b/pd/src/s_audio_alsa.c @@ -0,0 +1,630 @@ +/* Copyright (c) 1997-2003 Guenter Geiger, Miller Puckette, Larry Troxler, +* Winfried Ritsch, Karl MacMillan, and others. +* For information on usage and redistribution, and for a DISCLAIMER OF ALL +* WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ + +/* this file inputs and outputs audio using the ALSA API available on linux. */ + +#include + +#include "m_pd.h" +#include "s_stuff.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef int16_t t_alsa_sample16; +typedef int32_t t_alsa_sample32; +#define ALSA_SAMPLEWIDTH_16 sizeof(t_alsa_sample16) +#define ALSA_SAMPLEWIDTH_32 sizeof(t_alsa_sample32) +#define ALSA_XFERSIZE16 (signed int)(sizeof(t_alsa_sample16) * DEFDACBLKSIZE) +#define ALSA_XFERSIZE32 (signed int)(sizeof(t_alsa_sample32) * DEFDACBLKSIZE) +#define ALSA_MAXDEV 1 +#define ALSA_JITTER 1024 +#define ALSA_EXTRABUFFER 2048 +#define ALSA_DEFFRAGSIZE 64 +#define ALSA_DEFNFRAG 12 + +#ifndef INT32_MAX +#define INT32_MAX 0x7fffffff +#endif + +typedef struct _alsa_dev +{ + snd_pcm_t *inhandle; + snd_pcm_t *outhandle; +} t_alsa_dev; + +t_alsa_dev alsa_device; +static short *alsa_buf; +static int alsa_samplewidth; +static snd_pcm_status_t* in_status; +static snd_pcm_status_t* out_status; + +static int alsa_mode; + +/* Defines */ +#define DEBUG(x) x +#define DEBUG2(x) {x;} + +static char alsa_devname[512] = "hw:0,0"; +static int alsa_use_plugin = 0; + + /* don't assume we can turn all 31 bits when doing float-to-fix; + otherwise some audio drivers (e.g. Midiman/ALSA) wrap around. */ +#define FMAX 0x7ffff000 +#define CLIP32(x) (((x)>FMAX)?FMAX:((x) < -FMAX)?-FMAX:(x)) + + /* ugly Alsa-specific flags */ +void linux_alsa_devname(char *devname) +{ + strncpy(alsa_devname, devname, 511); +} + +void linux_alsa_use_plugin(int t) +{ + if (t == 1) + alsa_use_plugin = 1; + else + alsa_use_plugin = 0; +} + +/* support for ALSA pcmv2 api by Karl MacMillan */ + +static void check_error(int err, const char *why) +{ + if (err < 0) + fprintf(stderr, "%s: %s\n", why, snd_strerror(err)); +} + +int alsa_open_audio(int wantinchans, int wantoutchans, int srate) +{ + int err, inchans = 0, outchans = 0, subunitdir; + char devname[512]; + snd_pcm_hw_params_t* hw_params; + snd_pcm_sw_params_t* sw_params; + snd_output_t* out; + int frag_size = (sys_blocksize ? sys_blocksize : ALSA_DEFFRAGSIZE); + int nfrags, i; + short* tmp_buf; + unsigned int tmp_uint; + + nfrags = sys_schedadvance * (float)srate / (1e6 * frag_size); + + if (sys_verbose) + post("audio buffer set to %d", (int)(0.001 * sys_schedadvance)); + if (wantinchans) + { + err = snd_pcm_open(&alsa_device.inhandle, alsa_devname, + SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK); + + check_error(err, "snd_pcm_open (input)"); + if (err < 0) + inchans = 0; + else + { + inchans = wantinchans; + snd_pcm_nonblock(alsa_device.inhandle, 1); + } + } + if (wantoutchans) + { + err = snd_pcm_open(&alsa_device.outhandle, alsa_devname, + SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); + + check_error(err, "snd_pcm_open (output)"); + if (err < 0) + outchans = 0; + else + { + outchans = wantoutchans; + snd_pcm_nonblock(alsa_device.outhandle, 1); + } + } + if (inchans) + { + if (sys_verbose) + post("opening sound input..."); + err = snd_pcm_hw_params_malloc(&hw_params); + check_error(err, "snd_pcm_hw_params_malloc (input)"); + + // get the default params + err = snd_pcm_hw_params_any(alsa_device.inhandle, hw_params); + check_error(err, "snd_pcm_hw_params_any (input)"); + // set interleaved access - FIXME deal with other access types + err = snd_pcm_hw_params_set_access(alsa_device.inhandle, hw_params, + SND_PCM_ACCESS_RW_INTERLEAVED); + check_error(err, "snd_pcm_hw_params_set_access (input)"); + // Try to set 32 bit format first + err = snd_pcm_hw_params_set_format(alsa_device.inhandle, hw_params, + SND_PCM_FORMAT_S32); + if (err < 0) + { + /* fprintf(stderr, + "PD-ALSA: 32 bit format not available - using 16\n"); */ + err = snd_pcm_hw_params_set_format(alsa_device.inhandle, hw_params, + SND_PCM_FORMAT_S16); + check_error(err, "snd_pcm_hw_params_set_format (input)"); + alsa_samplewidth = 2; + } + else + { + alsa_samplewidth = 4; + } + post("Sample width set to %d bytes", alsa_samplewidth); + // set the subformat + err = snd_pcm_hw_params_set_subformat(alsa_device.inhandle, hw_params, + SND_PCM_SUBFORMAT_STD); + check_error(err, "snd_pcm_hw_params_set_subformat (input)"); + // set the number of channels + tmp_uint = inchans; + err = snd_pcm_hw_params_set_channels_min(alsa_device.inhandle, + hw_params, &tmp_uint); + check_error(err, "snd_pcm_hw_params_set_channels (input)"); + if (tmp_uint != (unsigned)inchans) + post("ALSA: set input channels to %d", tmp_uint); + inchans = tmp_uint; + // set the sampling rate + err = snd_pcm_hw_params_set_rate_min(alsa_device.inhandle, hw_params, + &srate, 0); + check_error(err, "snd_pcm_hw_params_set_rate_min (input)"); +#if 0 + err = snd_pcm_hw_params_get_rate(hw_params, &subunitdir); + post("input sample rate %d", err); +#endif + // set the period - ie frag size + // post("fragsize a %d", frag_size); + + /* LATER try this to get a recommended period size... + right now, it trips an assertion failure in ALSA lib */ +#if 0 + post("input period was %d, min %d, max %d\n", + snd_pcm_hw_params_get_period_size(hw_params, 0), + snd_pcm_hw_params_get_period_size_min(hw_params, 0), + snd_pcm_hw_params_get_period_size_max(hw_params, 0)); +#endif + err = snd_pcm_hw_params_set_period_size_near(alsa_device.inhandle, + hw_params, + (snd_pcm_uframes_t) + frag_size, 0); + check_error(err, "snd_pcm_hw_params_set_period_size_near (input)"); + // post("fragsize b %d", frag_size); + // set the number of periods - ie numfrags + // post("nfrags a %d", nfrags); + err = snd_pcm_hw_params_set_periods_near(alsa_device.inhandle, + hw_params, nfrags, 0); + check_error(err, "snd_pcm_hw_params_set_periods_near (input)"); + // set the buffer size + err = snd_pcm_hw_params_set_buffer_size_near(alsa_device.inhandle, + hw_params, nfrags * frag_size); + check_error(err, "snd_pcm_hw_params_set_buffer_size_near (input)"); + + err = snd_pcm_hw_params(alsa_device.inhandle, hw_params); + check_error(err, "snd_pcm_hw_params (input)"); + + snd_pcm_hw_params_free(hw_params); + + err = snd_pcm_sw_params_malloc(&sw_params); + check_error(err, "snd_pcm_sw_params_malloc (input)"); + err = snd_pcm_sw_params_current(alsa_device.inhandle, sw_params); + check_error(err, "snd_pcm_sw_params_current (input)"); +#if 1 + err = snd_pcm_sw_params_set_start_mode(alsa_device.inhandle, sw_params, + SND_PCM_START_EXPLICIT); + check_error(err, "snd_pcm_sw_params_set_start_mode (input)"); + err = snd_pcm_sw_params_set_xrun_mode(alsa_device.inhandle, sw_params, + SND_PCM_XRUN_NONE); + check_error(err, "snd_pcm_sw_params_set_xrun_mode (input)"); +#else + err = snd_pcm_sw_params_set_start_threshold(alsa_device.inhandle, + sw_params, nfrags * frag_size); + check_error(err, "snd_pcm_sw_params_set_start_threshold (input)"); + err = snd_pcm_sw_params_set_stop_threshold(alsa_device.inhandle, + sw_params, 1); + check_error(err, "snd_pcm_sw_params_set_stop_threshold (input)"); +#endif + + err = snd_pcm_sw_params_set_avail_min(alsa_device.inhandle, sw_params, + frag_size); + check_error(err, "snd_pcm_sw_params_set_avail_min (input)"); + err = snd_pcm_sw_params(alsa_device.inhandle, sw_params); + check_error(err, "snd_pcm_sw_params (input)"); + + snd_pcm_sw_params_free(sw_params); + + snd_output_stdio_attach(&out, stderr, 0); +#if 0 + if (sys_verbose) + { + snd_pcm_dump_hw_setup(alsa_device.inhandle, out); + snd_pcm_dump_sw_setup(alsa_device.inhandle, out); + } +#endif + } + + if (outchans) + { + int foo; + if (sys_verbose) + post("opening sound output..."); + err = snd_pcm_hw_params_malloc(&hw_params); + check_error(err, "snd_pcm_sw_params (output)"); + + // get the default params + err = snd_pcm_hw_params_any(alsa_device.outhandle, hw_params); + check_error(err, "snd_pcm_hw_params_any (output)"); + // set interleaved access - FIXME deal with other access types + err = snd_pcm_hw_params_set_access(alsa_device.outhandle, hw_params, + SND_PCM_ACCESS_RW_INTERLEAVED); + check_error(err, "snd_pcm_hw_params_set_access (output)"); + // Try to set 32 bit format first + err = snd_pcm_hw_params_set_format(alsa_device.outhandle, hw_params, + SND_PCM_FORMAT_S32); + if (err < 0) + { + err = snd_pcm_hw_params_set_format(alsa_device.outhandle, + hw_params,SND_PCM_FORMAT_S16); + check_error(err, "snd_pcm_hw_params_set_format (output)"); + /* fprintf(stderr, + "PD-ALSA: 32 bit format not available - using 16\n"); */ + alsa_samplewidth = 2; + } + else + { + alsa_samplewidth = 4; + } + // set the subformat + err = snd_pcm_hw_params_set_subformat(alsa_device.outhandle, hw_params, + SND_PCM_SUBFORMAT_STD); + check_error(err, "snd_pcm_hw_params_set_subformat (output)"); + // set the number of channels + tmp_uint = outchans; + err = snd_pcm_hw_params_set_channels_min(alsa_device.outhandle, + hw_params, &tmp_uint); + check_error(err, "snd_pcm_hw_params_set_channels (output)"); + if (tmp_uint != (unsigned)outchans) + post("alsa: set output channels to %d", tmp_uint); + outchans = tmp_uint; + // set the sampling rate + err = snd_pcm_hw_params_set_rate_min(alsa_device.outhandle, hw_params, + &srate, 0); + check_error(err, "snd_pcm_hw_params_set_rate_min (output)"); +#if 0 + err = snd_pcm_hw_params_get_rate(hw_params, &subunitdir); + post("output sample rate %d", err); +#endif + // set the period - ie frag size +#if 0 + post("output period was %d, min %d, max %d\n", + snd_pcm_hw_params_get_period_size(hw_params, 0), + snd_pcm_hw_params_get_period_size_min(hw_params, 0), + snd_pcm_hw_params_get_period_size_max(hw_params, 0)); +#endif + // post("fragsize c %d", frag_size); + err = snd_pcm_hw_params_set_period_size_near(alsa_device.outhandle, + hw_params, + (snd_pcm_uframes_t) + frag_size, 0); + // post("fragsize d %d", frag_size); + check_error(err, "snd_pcm_hw_params_set_period_size_near (output)"); + // set the number of periods - ie numfrags + err = snd_pcm_hw_params_set_periods_near(alsa_device.outhandle, + hw_params, nfrags, 0); + check_error(err, "snd_pcm_hw_params_set_periods_near (output)"); + // set the buffer size + err = snd_pcm_hw_params_set_buffer_size_near(alsa_device.outhandle, + hw_params, nfrags * frag_size); + + check_error(err, "snd_pcm_hw_params_set_buffer_size_near (output)"); + + err = snd_pcm_hw_params(alsa_device.outhandle, hw_params); + check_error(err, "snd_pcm_hw_params (output)"); + + snd_pcm_hw_params_free(hw_params); + + err = snd_pcm_sw_params_malloc(&sw_params); + check_error(err, "snd_pcm_sw_params_malloc (output)"); + err = snd_pcm_sw_params_current(alsa_device.outhandle, sw_params); + check_error(err, "snd_pcm_sw_params_current (output)"); +#if 1 + err = snd_pcm_sw_params_set_start_mode(alsa_device.outhandle, + sw_params, + SND_PCM_START_EXPLICIT); + check_error(err, "snd_pcm_sw_params_set_start_mode (output)"); + err = snd_pcm_sw_params_set_xrun_mode(alsa_device.outhandle, sw_params, + SND_PCM_XRUN_NONE); + check_error(err, "snd_pcm_sw_params_set_xrun_mode (output)"); +#else + err = snd_pcm_sw_params_set_start_threshold(alsa_device.inhandle, + sw_params, nfrags * frag_size); + check_error(err, "snd_pcm_sw_params_set_start_threshold (output)"); + err = snd_pcm_sw_params_set_stop_threshold(alsa_device.inhandle, + sw_params, 1); + check_error(err, "snd_pcm_sw_params_set_stop_threshold (output)"); +#endif + + err = snd_pcm_sw_params_set_avail_min(alsa_device.outhandle, sw_params, + frag_size); + check_error(err, "snd_pcm_sw_params_set_avail_min (output)"); + err = snd_pcm_sw_params(alsa_device.outhandle, sw_params); + check_error(err, "snd_pcm_sw_params (output)"); + + snd_pcm_sw_params_free(sw_params); + + snd_output_stdio_attach(&out, stderr, 0); +#if 0 + if (sys_verbose) + { + snd_pcm_dump_hw_setup(alsa_device.outhandle, out); + snd_pcm_dump_sw_setup(alsa_device.outhandle, out); + } +#endif + } + + if (inchans) + snd_pcm_prepare(alsa_device.inhandle); + if (outchans) + snd_pcm_prepare(alsa_device.outhandle); + + // if duplex we can link the channels so they start together + if (inchans && outchans) + snd_pcm_link(alsa_device.inhandle, alsa_device.outhandle); + + // set up the buffer + if (outchans > inchans) + alsa_buf = (short *)calloc(sizeof(char) * alsa_samplewidth, + DEFDACBLKSIZE * outchans); + else + alsa_buf = (short *)calloc(sizeof(char) * alsa_samplewidth, + DEFDACBLKSIZE * inchans); + // fill the buffer with silence + if (outchans) + { + i = nfrags + 1; + while (i--) + snd_pcm_writei(alsa_device.outhandle, alsa_buf, frag_size); + } + + // set up the status variables + err = snd_pcm_status_malloc(&in_status); + check_error(err, "snd_pcm_status_malloc"); + err = snd_pcm_status_malloc(&out_status); + check_error(err, "snd_pcm_status_malloc"); + + // start the device +#if 1 + if (outchans) + { + err = snd_pcm_start(alsa_device.outhandle); + check_error(err, "snd_pcm_start"); + } + else if (inchans) + { + err = snd_pcm_start(alsa_device.inhandle); + check_error(err, "snd_pcm_start"); + } +#endif + + return 0; +} + +void alsa_close_audio(void) +{ + int err; + if (sys_inchannels) + { + err = snd_pcm_close(alsa_device.inhandle); + check_error(err, "snd_pcm_close (input)"); + } + if (sys_outchannels) + { + err = snd_pcm_close(alsa_device.outhandle); + check_error(err, "snd_pcm_close (output)"); + } +} + +// #define DEBUG_ALSA_XFER + +int alsa_send_dacs(void) +{ + static int16_t *sp; + static int xferno = 0; + static int callno = 0; + static double timenow; + double timelast; + t_sample *fp, *fp1, *fp2; + int i, j, k, err, devno = 0; + int inputcount = 0, outputcount = 0, inputlate = 0, outputlate = 0; + int result; + int inchannels = sys_inchannels; + int outchannels = sys_outchannels; + unsigned int intransfersize = DEFDACBLKSIZE; + unsigned int outtransfersize = DEFDACBLKSIZE; + + // get the status + if (!inchannels && !outchannels) + { + return SENDDACS_NO; + } + + timelast = timenow; + timenow = sys_getrealtime(); + +#ifdef DEBUG_ALSA_XFER + if (timenow - timelast > 0.050) + fprintf(stderr, "(%d)", + (int)(1000 * (timenow - timelast))), fflush(stderr); +#endif + + callno++; + + if (inchannels) + { + snd_pcm_status(alsa_device.inhandle, in_status); + if (snd_pcm_status_get_avail(in_status) < intransfersize) + return SENDDACS_NO; + } + if (outchannels) + { + snd_pcm_status(alsa_device.outhandle, out_status); + if (snd_pcm_status_get_avail(out_status) < outtransfersize) + return SENDDACS_NO; + } + + /* do output */ + if (outchannels) + { + fp = sys_soundout; + if (alsa_samplewidth == 4) + { + for (i = 0, fp1 = fp; i < outchannels; i++, fp1 += DEFDACBLKSIZE) + { + for (j = i, k = DEFDACBLKSIZE, fp2 = fp1; k--; + j += outchannels, fp2++) + { + float s1 = *fp2 * INT32_MAX; + ((t_alsa_sample32 *)alsa_buf)[j] = CLIP32(s1); + } + } + } + else + { + for (i = 0, fp1 = fp; i < outchannels; i++, fp1 += DEFDACBLKSIZE) + { + for (j = i, k = DEFDACBLKSIZE, fp2 = fp1; k--; + j += outchannels, fp2++) + { + int s = *fp2 * 32767.; + if (s > 32767) + s = 32767; + else if (s < -32767) + s = -32767; + ((t_alsa_sample16 *)alsa_buf)[j] = s; + } + } + } + + result = snd_pcm_writei(alsa_device.outhandle, alsa_buf, + outtransfersize); + if (result != (int)outtransfersize) + { + #ifdef DEBUG_ALSA_XFER + if (result >= 0 || errno == EAGAIN) + fprintf(stderr, "ALSA: write returned %d of %d\n", + result, outtransfersize); + else fprintf(stderr, "ALSA: write: %s\n", + snd_strerror(errno)); + fprintf(stderr, + "inputcount %d, outputcount %d, outbufsize %d\n", + inputcount, outputcount, + (ALSA_EXTRABUFFER + sys_advance_samples) + * alsa_samplewidth * outchannels); + #endif + sys_log_error(ERR_DACSLEPT); + return (SENDDACS_NO); + } + + /* zero out the output buffer */ + memset(sys_soundout, 0, DEFDACBLKSIZE * sizeof(*sys_soundout) * + sys_outchannels); + if (sys_getrealtime() - timenow > 0.002) + { + #ifdef DEBUG_ALSA_XFER + fprintf(stderr, "output %d took %d msec\n", + callno, (int)(1000 * (timenow - timelast))), fflush(stderr); + #endif + timenow = sys_getrealtime(); + sys_log_error(ERR_DACSLEPT); + } + } + /* do input */ + if (sys_inchannels) + { + result = snd_pcm_readi(alsa_device.inhandle, alsa_buf, intransfersize); + if (result < (int)intransfersize) + { +#ifdef DEBUG_ALSA_XFER + if (result < 0) + fprintf(stderr, + "snd_pcm_read %d %d: %s\n", + callno, xferno, snd_strerror(errno)); + else fprintf(stderr, + "snd_pcm_read %d %d returned only %d\n", + callno, xferno, result); + fprintf(stderr, + "inputcount %d, outputcount %d, inbufsize %d\n", + inputcount, outputcount, + (ALSA_EXTRABUFFER + sys_advance_samples) + * alsa_samplewidth * inchannels); +#endif + sys_log_error(ERR_ADCSLEPT); + return (SENDDACS_NO); + } + fp = sys_soundin; + if (alsa_samplewidth == 4) + { + for (i = 0, fp1 = fp; i < inchannels; i++, fp1 += DEFDACBLKSIZE) + { + for (j = i, k = DEFDACBLKSIZE, fp2 = fp1; k--; + j += inchannels, fp2++) + *fp2 = (float) ((t_alsa_sample32 *)alsa_buf)[j] + * (1./ INT32_MAX); + } + } + else + { + for (i = 0, fp1 = fp; i < inchannels; i++, fp1 += DEFDACBLKSIZE) + { + for (j = i, k = DEFDACBLKSIZE, fp2 = fp1; k--; j += inchannels, + fp2++) + *fp2 = (float) ((t_alsa_sample16 *)alsa_buf)[j] + * 3.051850e-05; + } + } + } + xferno++; + if (sys_getrealtime() - timenow > 0.002) + { +#ifdef DEBUG_ALSA_XFER + fprintf(stderr, "routine took %d msec\n", + (int)(1000 * (sys_getrealtime() - timenow))); +#endif + sys_log_error(ERR_ADCSLEPT); + } + return SENDDACS_YES; +} + +void alsa_resync( void) +{ + int i, result; + if (sys_audioapi != API_ALSA) + { + error("restart-audio: implemented for ALSA only."); + return; + } + memset(alsa_buf, 0, + sizeof(char) * alsa_samplewidth * DEFDACBLKSIZE * sys_outchannels); + for (i = 0; i < 1000000; i++) + { + result = snd_pcm_writei(alsa_device.outhandle, alsa_buf, + DEFDACBLKSIZE); + if (result != (int)DEFDACBLKSIZE) + break; + } + post("%d written", i); +} + +void alsa_listdevs( void) +{ + post("device listing not implemented in ALSA yet\n"); +} + -- cgit v1.2.1