From 64fdb009695828b788fce074135b20a5e52c5fc4 Mon Sep 17 00:00:00 2001 From: Thomas Grill Date: Tue, 23 Sep 2003 00:21:28 +0000 Subject: imported version 0.37-0 svn path=/trunk/; revision=1016 --- pd/src/s_audio_alsa.c | 328 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 239 insertions(+), 89 deletions(-) (limited to 'pd/src/s_audio_alsa.c') diff --git a/pd/src/s_audio_alsa.c b/pd/src/s_audio_alsa.c index 0fa3d791..b94a4e04 100644 --- a/pd/src/s_audio_alsa.c +++ b/pd/src/s_audio_alsa.c @@ -45,39 +45,27 @@ typedef struct _alsa_dev } t_alsa_dev; t_alsa_dev alsa_device; -static short *alsa_buf; +static short *alsa_buf = 0; static int alsa_samplewidth; static snd_pcm_status_t* in_status; static snd_pcm_status_t* out_status; static int alsa_mode; +static int alsa_buf_samps; /* believed actual ALSA bufsize in sample frames */ +static int alsa_inchannels; +static int alsa_outchannels; /* Defines */ #define DEBUG(x) x #define DEBUG2(x) {x;} -static char alsa_devname[512] = "hw:0,0"; -static int alsa_use_plugin = 0; +static void alsa_checkiosync( void); /* 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) @@ -86,7 +74,11 @@ static void check_error(int err, const char *why) fprintf(stderr, "%s: %s\n", why, snd_strerror(err)); } -int alsa_open_audio(int wantinchans, int wantoutchans, int srate) +/* was: alsa_open_audio(int wantinchans, int wantoutchans, int srate) */ + +int alsa_open_audio(int naudioindev, int *audioindev, int nchindev, + int *chindev, int naudiooutdev, int *audiooutdev, int nchoutdev, + int *choutdev, int rate) { int err, inchans = 0, outchans = 0, subunitdir; char devname[512]; @@ -97,14 +89,42 @@ int alsa_open_audio(int wantinchans, int wantoutchans, int srate) int nfrags, i; short* tmp_buf; unsigned int tmp_uint; + int wantinchans, wantoutchans, devno; + + if (naudioindev >= 2 || naudiooutdev >= 2) + post("alsa: only one input and output device allowed (extras ignored"); + if (naudioindev >= 1 && naudiooutdev >= 1 && + audioindev[0] != audiooutdev[0]) + post("alsa: changing output device to agree with input device"); + if (nchindev) + wantinchans = chindev[0]; + else wantinchans = (naudioindev ? 2 : 0); + if (nchoutdev) + wantoutchans = choutdev[0]; + else wantoutchans = (naudiooutdev ? 2 : 0); + devno = (naudioindev > 0 ? audioindev[0] : + (naudiooutdev > 0 ? audiooutdev[0] : 0)); + + /* device names are hw:0, plughw:0, hw:1, and so on. */ + if (devno & 1) + sprintf(devname, "plughw:%d", devno/2); + else sprintf(devname, "hw:%d", devno/2); - nfrags = sys_schedadvance * (float)srate / (1e6 * frag_size); + if (sys_verbose) + post("device name %s; channels in %d, out %d", devname, wantinchans, + wantoutchans); + + nfrags = sys_schedadvance * (float)rate / (1e6 * frag_size); + /* save our belief as to ALSA's buffer size for later */ + alsa_buf_samps = nfrags * 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, + err = snd_pcm_open(&alsa_device.inhandle, devname, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK); check_error(err, "snd_pcm_open (input)"); @@ -118,7 +138,7 @@ int alsa_open_audio(int wantinchans, int wantoutchans, int srate) } if (wantoutchans) { - err = snd_pcm_open(&alsa_device.outhandle, alsa_devname, + err = snd_pcm_open(&alsa_device.outhandle, devname, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); check_error(err, "snd_pcm_open (output)"); @@ -175,7 +195,7 @@ int alsa_open_audio(int wantinchans, int wantoutchans, int srate) inchans = tmp_uint; // set the sampling rate err = snd_pcm_hw_params_set_rate_min(alsa_device.inhandle, hw_params, - &srate, 0); + &rate, 0); check_error(err, "snd_pcm_hw_params_set_rate_min (input)"); #if 0 err = snd_pcm_hw_params_get_rate(hw_params, &subunitdir); @@ -191,7 +211,7 @@ int alsa_open_audio(int wantinchans, int wantoutchans, int srate) 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 +#endif err = snd_pcm_hw_params_set_period_size_near(alsa_device.inhandle, hw_params, (snd_pcm_uframes_t) @@ -217,22 +237,12 @@ int alsa_open_audio(int wantinchans, int wantoutchans, int srate) 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); + sw_params, 0x7fffffff); 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)"); @@ -296,7 +306,7 @@ int alsa_open_audio(int wantinchans, int wantoutchans, int srate) outchans = tmp_uint; // set the sampling rate err = snd_pcm_hw_params_set_rate_min(alsa_device.outhandle, hw_params, - &srate, 0); + &rate, 0); check_error(err, "snd_pcm_hw_params_set_rate_min (output)"); #if 0 err = snd_pcm_hw_params_get_rate(hw_params, &subunitdir); @@ -325,39 +335,27 @@ int alsa_open_audio(int wantinchans, int wantoutchans, int srate) 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, + err = snd_pcm_sw_params_set_start_threshold(alsa_device.outhandle, 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); + err = snd_pcm_sw_params_set_stop_threshold(alsa_device.outhandle, + sw_params, 0x7fffffff); 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); + 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); @@ -379,40 +377,36 @@ int alsa_open_audio(int wantinchans, int wantoutchans, int srate) 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 + // set up the buffer + if (alsa_buf) + free(alsa_buf); + alsa_buf = (short *)malloc( + sizeof(char) * alsa_samplewidth * DEFDACBLKSIZE * + (outchans > inchans ? outchans : inchans)); + memset(alsa_buf, 0, sizeof(char) * alsa_samplewidth * DEFDACBLKSIZE * + (outchans > inchans ? outchans : inchans)); + // fill the buffer with silence 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"); + i = (frag_size * nfrags)/DEFDACBLKSIZE + 1; + while (i--) + snd_pcm_writei(alsa_device.outhandle, alsa_buf, DEFDACBLKSIZE); + /* apparently we're not suppposed to start it in this case, + but can (and must) if there's only ADC open (below). */ + /* if ((err = snd_pcm_start(alsa_device.outhandle) < 0)) + check_error(err, "output start failed\n"); */ } -#endif + else if (snd_pcm_start(alsa_device.inhandle) < 0) + check_error(err, "input start failed\n"); + + alsa_outchannels = outchans; + alsa_inchannels = inchans; return 0; } @@ -445,8 +439,10 @@ int alsa_send_dacs(void) 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; + int inchannels = (sys_inchannels > alsa_inchannels ? + alsa_inchannels : sys_inchannels); + int outchannels = (sys_outchannels > alsa_outchannels ? + alsa_outchannels : sys_outchannels); unsigned int intransfersize = DEFDACBLKSIZE; unsigned int outtransfersize = DEFDACBLKSIZE; @@ -467,13 +463,15 @@ int alsa_send_dacs(void) callno++; - if (inchannels) + alsa_checkiosync(); /* check I/O are in sync and data not late */ + + if (alsa_inchannels) { snd_pcm_status(alsa_device.inhandle, in_status); if (snd_pcm_status_get_avail(in_status) < intransfersize) return SENDDACS_NO; } - if (outchannels) + if (alsa_outchannels) { snd_pcm_status(alsa_device.outhandle, out_status); if (snd_pcm_status_get_avail(out_status) < outtransfersize) @@ -481,7 +479,7 @@ int alsa_send_dacs(void) } /* do output */ - if (outchannels) + if (alsa_outchannels) { fp = sys_soundout; if (alsa_samplewidth == 4) @@ -489,7 +487,7 @@ int alsa_send_dacs(void) for (i = 0, fp1 = fp; i < outchannels; i++, fp1 += DEFDACBLKSIZE) { for (j = i, k = DEFDACBLKSIZE, fp2 = fp1; k--; - j += outchannels, fp2++) + j += alsa_outchannels, fp2++) { float s1 = *fp2 * INT32_MAX; ((t_alsa_sample32 *)alsa_buf)[j] = CLIP32(s1); @@ -501,7 +499,7 @@ int alsa_send_dacs(void) for (i = 0, fp1 = fp; i < outchannels; i++, fp1 += DEFDACBLKSIZE) { for (j = i, k = DEFDACBLKSIZE, fp2 = fp1; k--; - j += outchannels, fp2++) + j += alsa_outchannels, fp2++) { int s = *fp2 * 32767.; if (s > 32767) @@ -547,7 +545,7 @@ int alsa_send_dacs(void) } } /* do input */ - if (sys_inchannels) + if (alsa_inchannels) { result = snd_pcm_readi(alsa_device.inhandle, alsa_buf, intransfersize); if (result < (int)intransfersize) @@ -575,7 +573,7 @@ int alsa_send_dacs(void) for (i = 0, fp1 = fp; i < inchannels; i++, fp1 += DEFDACBLKSIZE) { for (j = i, k = DEFDACBLKSIZE, fp2 = fp1; k--; - j += inchannels, fp2++) + j += alsa_inchannels, fp2++) *fp2 = (float) ((t_alsa_sample32 *)alsa_buf)[j] * (1./ INT32_MAX); } @@ -584,8 +582,8 @@ int alsa_send_dacs(void) { for (i = 0, fp1 = fp; i < inchannels; i++, fp1 += DEFDACBLKSIZE) { - for (j = i, k = DEFDACBLKSIZE, fp2 = fp1; k--; j += inchannels, - fp2++) + for (j = i, k = DEFDACBLKSIZE, fp2 = fp1; k--; + j += alsa_inchannels, fp2++) *fp2 = (float) ((t_alsa_sample16 *)alsa_buf)[j] * 3.051850e-05; } @@ -603,6 +601,35 @@ int alsa_send_dacs(void) return SENDDACS_YES; } +void alsa_printstate( void) +{ + int i, result; + snd_pcm_sframes_t indelay, outdelay; + if (sys_audioapi != API_ALSA) + { + error("restart-audio: implemented for ALSA only."); + return; + } + if (sys_inchannels) + { + result = snd_pcm_delay(alsa_device.inhandle, &indelay); + if (result < 0) + post("snd_pcm_delay 1 failed"); + else post("in delay %d", indelay); + } + if (sys_outchannels) + { + result = snd_pcm_delay(alsa_device.outhandle, &outdelay); + if (result < 0) + post("snd_pcm_delay 2 failed"); + else post("out delay %d", outdelay); + } + post("sum %d (%d mod 64)\n", indelay + outdelay, (indelay+outdelay)%64); + + post("buf samples %d", alsa_buf_samps); +} + + void alsa_resync( void) { int i, result; @@ -623,8 +650,131 @@ void alsa_resync( void) post("%d written", i); } +void alsa_putzeros(int n) +{ + int i, result; + memset(alsa_buf, 0, + sizeof(char) * alsa_samplewidth * DEFDACBLKSIZE * alsa_outchannels); + for (i = 0; i < n; i++) + { + result = snd_pcm_writei(alsa_device.outhandle, alsa_buf, DEFDACBLKSIZE); +#if 0 + if (result != DEFDACBLKSIZE) + post("result %d", result); +#endif + } +} + +void alsa_getzeros(int n) +{ + int i, result; + for (i = 0; i < n; i++) + { + result = snd_pcm_readi(alsa_device.inhandle, alsa_buf, DEFDACBLKSIZE); +#if 0 + if (result != DEFDACBLKSIZE) + post("result %d", result); +#endif + } +} + + /* call this only if both input and output are open */ +static void alsa_checkiosync( void) +{ + int i, result, checkit = 1, giveup = 1000, alreadylogged = 0; + snd_pcm_sframes_t indelay, outdelay, defect; + + if (!(alsa_outchannels && alsa_inchannels)) + return; + while (checkit) + { + checkit = 0; + if (giveup-- <= 0) + return; + result = snd_pcm_delay(alsa_device.outhandle, &outdelay); + if (result < 0) + { + post("output snd_pcm_delay failed: %s", snd_strerror(result)); + if (snd_pcm_status(alsa_device.outhandle, out_status) < 0) + post("output snd_pcm_status failed"); + else post("astate %d", + snd_pcm_status_get_state(out_status)); + return; + } + if (outdelay < 0) + sys_log_error(ERR_DATALATE), alreadylogged = 1; + + if (sys_inchannels) + { + result = snd_pcm_delay(alsa_device.inhandle, &indelay); + if (result < 0) + { + post("input snd_pcm_delay failed"); + return; + } + defect = indelay + outdelay - alsa_buf_samps; + if (defect < -DEFDACBLKSIZE) + { + checkit = 1; + alsa_putzeros(1); + if (!alreadylogged) + sys_log_error(ERR_RESYNC), alreadylogged = 1; + } + else if (defect > 0) + { + checkit = 1; + alsa_getzeros(1); + if (!alreadylogged) + sys_log_error(ERR_RESYNC), alreadylogged = 1; + } + } + } +} + void alsa_listdevs( void) { post("device listing not implemented in ALSA yet\n"); } + /* For each hardware card found, we list two devices, the "hard" and + "plug" one. The card scan is derived from portaudio code. */ +void alsa_getdevs(char *indevlist, int *nindevs, + char *outdevlist, int *noutdevs, int *canmulti, + int maxndev, int devdescsize) +{ + int ndev = 0, cardno = -1; + *canmulti = 0; /* only one device; must be the same for input&output */ + while (!snd_card_next(&cardno) && cardno >= 0) + { + snd_ctl_t *ctl; + snd_ctl_card_info_t *info; + char devname[80]; + const char *desc; + if (2 * ndev + 2 > maxndev) + break; + /* apparently, "cardno" is just a counter; but check that here */ + if (ndev != cardno) + fprintf(stderr, "oops: ALSA cards not reported in order?\n"); + sprintf(devname, "hw:%d", cardno ); + /* fprintf(stderr, "\ntry %s...\n", devname); */ + if (snd_ctl_open(&ctl, devname, 0) >= 0) + { + snd_ctl_card_info_malloc(&info); + snd_ctl_card_info(ctl, info); + desc = snd_ctl_card_info_get_name(info); + snd_ctl_card_info_free(info); + } + else + { + fprintf(stderr, "ALSA card scan error\n"); + desc = "???"; + } + /* fprintf(stderr, "name: %s\n", snd_ctl_card_info_get_name(info)); */ + sprintf(indevlist + 2*ndev * devdescsize, "%s (hardware)", desc); + sprintf(indevlist + (2*ndev + 1) * devdescsize, "%s (plug-in)", desc); + sprintf(outdevlist + 2*ndev * devdescsize, "%s (hardware)", desc); + sprintf(outdevlist + (2*ndev + 1) * devdescsize, "%s (plug-in)", desc); + ndev++; + } + *nindevs = *noutdevs = 2 * ndev; +} -- cgit v1.2.1