ref: 4976f1410cc69ae475fdaedc574a404ed606dafd
dir: /src/alsa.c/
/* * Copyright 1997-2005 Jimen Ching And Sundry Contributors * This source code is freely redistributable and may be used for * any purpose. This copyright notice must be maintained. * Jimen Ching And Sundry Contributors are not * responsible for the consequences of using this software. */ /* ALSA sound driver * * converted to alsalib cbagwell 20050914 * added by Jimen Ching (jching@flex.com) 19990207 * based on info grabed from aplay.c in alsa-utils package. * Updated for ALSA 0.9.X API 20020824. */ #include "st_i.h" #if defined(HAVE_ALSA) #include <alsa/asoundlib.h> typedef struct alsa_priv { snd_pcm_t *pcm_handle; void *buf; st_ssize_t buf_size; } *alsa_priv_t; static int get_format(ft_t ft, snd_pcm_format_mask_t *fmask, int *fmt); extern void st_ub_write_buf(char* buf1, st_sample_t *buf2, st_ssize_t len, char swap); extern void st_sb_write_buf(char *buf1, st_sample_t *buf2, st_ssize_t len, char swap); extern void st_uw_write_buf(char *buf1, st_sample_t *buf2, st_ssize_t len, char swap); extern void st_sw_write_buf(char *buf1, st_sample_t *buf2, st_ssize_t len, char swap); extern void st_ub_read_buf(st_sample_t *buf1, char *buf2, st_ssize_t len, char swap); extern void st_sb_read_buf(st_sample_t *buf1, char *buf2, st_ssize_t len, char swap); extern void st_uw_read_buf(st_sample_t *buf1, char *buf2, st_ssize_t len, char swap); extern void st_sw_read_buf(st_sample_t *buf1, char *buf2, st_ssize_t len, char swap); int st_alsasetup(ft_t ft, snd_pcm_stream_t mode) { int fmt = SND_PCM_FORMAT_S16; int err; alsa_priv_t alsa = (alsa_priv_t)ft->priv; snd_pcm_hw_params_t *hw_params; unsigned int min_rate, max_rate; unsigned int min_chan, max_chan; unsigned int rate; int dir; snd_pcm_format_mask_t *fmask; /* Reserve buffer for 16-bit data. FIXME: Whats a good size? */ alsa->buf_size = ST_BUFSIZ*2; if ((alsa->buf = malloc(alsa->buf_size)) == NULL) { st_fail_errno(ft,ST_ENOMEM, "unable to allocate output buffer of size %d", ft->file.size); return(ST_EOF); } if ((err = snd_pcm_open(&(alsa->pcm_handle), ft->filename, mode, 0)) < 0) { st_fail_errno(ft, ST_EPERM, "cannot open audio device\n"); return ST_EOF; } if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) { st_fail_errno(ft, ST_ENOMEM, "cannot allocate hardware parameter structure\n"); return ST_EOF; } if ((err = snd_pcm_hw_params_any(alsa->pcm_handle, hw_params)) < 0) { st_fail_errno(ft, ST_EPERM, "cannot initialize hardware parameter structure\n"); return ST_EOF; } if ((err = snd_pcm_hw_params_set_access(alsa->pcm_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { st_fail_errno(ft, ST_EPERM, "cannot set access type\n"); return ST_EOF; } snd_pcm_hw_params_get_channels_min(hw_params, &min_chan); snd_pcm_hw_params_get_channels_max(hw_params, &max_chan); if (ft->info.channels == -1) ft->info.channels = min_chan; else if (ft->info.channels > max_chan) ft->info.channels = max_chan; else if (ft->info.channels < min_chan) ft->info.channels = min_chan; snd_pcm_format_mask_malloc(&fmask); snd_pcm_hw_params_get_format_mask(hw_params, fmask); if (get_format(ft, fmask, &fmt) < 0) return (ST_EOF); snd_pcm_format_mask_free(fmask); if ((err = snd_pcm_hw_params_set_format(alsa->pcm_handle, hw_params, fmt)) < 0) { st_fail_errno(ft, ST_EPERM, "cannot set sample format\n"); return ST_EOF; } snd_pcm_hw_params_get_rate_min(hw_params, &min_rate, &dir); snd_pcm_hw_params_get_rate_max(hw_params, &max_rate, &dir); rate = ft->info.rate; if (rate < min_rate) rate = min_rate; else if (rate > max_rate) rate = max_rate; if (rate != ft->info.rate) { st_warn("alsa: Hardware does not support %d. Forcing sample rate to %d.\n", ft->info.rate, rate); } dir = 0; if ((err = snd_pcm_hw_params_set_rate_near(alsa->pcm_handle, hw_params, &rate, &dir)) < 0) { st_fail_errno(ft, ST_EPERM, "cannot set sample rate\n"); return ST_EOF; } if (rate != ft->info.rate) { st_warn("Could not set exact rate of %d. Approximating with %d\n", ft->info.rate, rate); } snd_pcm_hw_params_get_rate(hw_params, &rate, &dir); if ((err = snd_pcm_hw_params_set_channels(alsa->pcm_handle, hw_params, ft->info.channels)) < 0) { st_fail_errno(ft, ST_EPERM, "cannot set channel count\n"); return ST_EOF; } #if 0 /* Set number of periods. Periods used to be called fragments. */ if (snd_pcm_hw_params_set_periods(alsa->pcm_handle, hw_params, 2, 0) < 0) { st_fail_errno(ft, ST_EPERM, "Error setting periods.\n"); return ST_EOF; } /* Set buffer size (in frames). The resulting latency is given by */ /* latency = periodsize * periods / (rate * bytes_per_frame) */ if (snd_pcm_hw_params_set_buffer_size(alsa->pcm_handle, hw_params, (ST_BUFSIZ * 8)>>2) < 0) { st_fail_errno(ft, ST_EPERM, "Error setting buffersize.\n"); return ST_EOF; } #endif if ((err = snd_pcm_hw_params(alsa->pcm_handle, hw_params)) < 0) { st_fail_errno(ft, ST_EPERM, "cannot set parameters\n"); return ST_EOF; } snd_pcm_hw_params_free(hw_params); if ((err = snd_pcm_prepare(alsa->pcm_handle)) < 0) { st_fail_errno(ft, ST_EPERM, "cannot prepare audio interface for use\n"); return ST_EOF; } sigintreg(ft); /* Prepare to catch SIGINT */ return (ST_SUCCESS); } /* * Do anything required before you start reading samples. * Read file header. * Find out sampling rate, * size and encoding of samples, * mono/stereo/quad. */ int st_alsastartread(ft_t ft) { return st_alsasetup(ft, SND_PCM_STREAM_CAPTURE); } st_ssize_t st_alsaread(ft_t ft, st_sample_t *buf, st_ssize_t nsamp) { st_ssize_t len; alsa_priv_t alsa = (alsa_priv_t)ft->priv; void (*read_buf)(st_sample_t *, char *, st_ssize_t, char) = 0; /* Check to see if user sent SIGINT and if so return ST_EOF to * stop playing. */ if (ft->file.eof) return ST_EOF; switch(ft->info.size) { case ST_SIZE_BYTE: switch(ft->info.encoding) { case ST_ENCODING_SIGN2: read_buf = st_sb_read_buf; break; case ST_ENCODING_UNSIGNED: read_buf = st_ub_read_buf; break; default: st_fail_errno(ft,ST_EFMT,"Do not support this encoding for this data size"); return ST_EOF; } break; case ST_SIZE_WORD: switch(ft->info.encoding) { case ST_ENCODING_SIGN2: read_buf = st_sw_read_buf; break; case ST_ENCODING_UNSIGNED: read_buf = st_uw_read_buf; break; default: st_fail_errno(ft,ST_EFMT,"Do not support this encoding for this data size"); return ST_EOF; } break; default: st_fail_errno(ft,ST_EFMT,"Do not support this data size for this handler"); return ST_EOF; } /* Prevent overflow */ if (nsamp > alsa->buf_size/ft->info.size) nsamp = (alsa->buf_size/ft->info.size); len = snd_pcm_readi(alsa->pcm_handle, alsa->buf, nsamp); read_buf(buf, alsa->buf, len, ft->swap); return len; } int st_alsastopread(ft) ft_t ft; { alsa_priv_t alsa = (alsa_priv_t)ft->priv; snd_pcm_close(alsa->pcm_handle); free(alsa->buf); return ST_SUCCESS; } int st_alsastartwrite(ft_t ft) { return st_alsasetup(ft, SND_PCM_STREAM_PLAYBACK); int fmt = SND_PCM_FORMAT_S16; int err; alsa_priv_t alsa = (alsa_priv_t)ft->priv; snd_pcm_hw_params_t *hw_params; unsigned int min_rate, max_rate; unsigned int min_chan, max_chan; unsigned int rate; int dir; snd_pcm_format_mask_t *fmask; /* Reserve buffer for 16-bit data. FIXME: Whats a good size? */ alsa->buf_size = ST_BUFSIZ*2; if ((alsa->buf = malloc(alsa->buf_size)) == NULL) { st_fail_errno(ft,ST_ENOMEM, "unable to allocate output buffer of size %d", ft->file.size); return(ST_EOF); } if ((err = snd_pcm_open(&(alsa->pcm_handle), ft->filename, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { st_fail_errno(ft, ST_EPERM, "cannot open audio device\n"); return ST_EOF; } if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) { st_fail_errno(ft, ST_ENOMEM, "cannot allocate hardware parameter structure\n"); return ST_EOF; } if ((err = snd_pcm_hw_params_any(alsa->pcm_handle, hw_params)) < 0) { st_fail_errno(ft, ST_EPERM, "cannot initialize hardware parameter structure\n"); return ST_EOF; } if ((err = snd_pcm_hw_params_set_access(alsa->pcm_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { st_fail_errno(ft, ST_EPERM, "cannot set access type\n"); return ST_EOF; } snd_pcm_hw_params_get_channels_min(hw_params, &min_chan); snd_pcm_hw_params_get_channels_max(hw_params, &max_chan); if (ft->info.channels == -1) ft->info.channels = min_chan; else if (ft->info.channels > max_chan) ft->info.channels = max_chan; else if (ft->info.channels < min_chan) ft->info.channels = min_chan; snd_pcm_format_mask_malloc(&fmask); snd_pcm_hw_params_get_format_mask(hw_params, fmask); if (get_format(ft, fmask, &fmt) < 0) return (ST_EOF); snd_pcm_format_mask_free(fmask); if ((err = snd_pcm_hw_params_set_format(alsa->pcm_handle, hw_params, fmt)) < 0) { st_fail_errno(ft, ST_EPERM, "cannot set sample format\n"); return ST_EOF; } snd_pcm_hw_params_get_rate_min(hw_params, &min_rate, &dir); snd_pcm_hw_params_get_rate_max(hw_params, &max_rate, &dir); rate = ft->info.rate; if (rate < min_rate) rate = min_rate; else if (rate > max_rate) rate = max_rate; if (rate != ft->info.rate) { st_warn("alsa: Hardware does not support %d. Forcing sample rate to %d.\n", ft->info.rate, rate); } dir = 0; if ((err = snd_pcm_hw_params_set_rate_near(alsa->pcm_handle, hw_params, &rate, &dir)) < 0) { st_fail_errno(ft, ST_EPERM, "cannot set sample rate\n"); return ST_EOF; } if (rate != ft->info.rate) { st_warn("Could not set exact rate of %d. Approximating with %d\n", ft->info.rate, rate); } snd_pcm_hw_params_get_rate(hw_params, &rate, &dir); if ((err = snd_pcm_hw_params_set_channels(alsa->pcm_handle, hw_params, ft->info.channels)) < 0) { st_fail_errno(ft, ST_EPERM, "cannot set channel count\n"); return ST_EOF; } #if 0 /* Set number of periods. Periods used to be called fragments. */ if (snd_pcm_hw_params_set_periods(alsa->pcm_handle, hw_params, 2, 0) < 0) { st_fail_errno(ft, ST_EPERM, "Error setting periods.\n"); return ST_EOF; } /* Set buffer size (in frames). The resulting latency is given by */ /* latency = periodsize * periods / (rate * bytes_per_frame) */ if (snd_pcm_hw_params_set_buffer_size(alsa->pcm_handle, hw_params, (ST_BUFSIZ * 2)>>2) < 0) { st_fail_errno(ft, ST_EPERM, "Error setting buffersize.\n"); return ST_EOF; } #endif if ((err = snd_pcm_hw_params(alsa->pcm_handle, hw_params)) < 0) { st_fail_errno(ft, ST_EPERM, "cannot set parameters\n"); return ST_EOF; } snd_pcm_hw_params_free(hw_params); if ((err = snd_pcm_prepare(alsa->pcm_handle)) < 0) { st_fail_errno(ft, ST_EPERM, "cannot prepare audio interface for use\n"); return ST_EOF; } sigintreg(ft); /* Prepare to catch SIGINT */ return (ST_SUCCESS); } st_ssize_t st_alsawrite(ft_t ft, st_sample_t *buf, st_ssize_t nsamp) { st_ssize_t len; alsa_priv_t alsa = (alsa_priv_t)ft->priv; void (*write_buf)(char *, st_sample_t *, st_ssize_t, char) = 0; /* Check to see if user sent SIGINT and if so return ST_EOF to * stop recording */ if (ft->file.eof) return ST_EOF; switch(ft->info.size) { case ST_SIZE_BYTE: switch(ft->info.encoding) { case ST_ENCODING_SIGN2: write_buf = st_sb_write_buf; break; case ST_ENCODING_UNSIGNED: write_buf = st_ub_write_buf; break; default: st_fail_errno(ft,ST_EFMT,"Do not support this encoding for this data size"); return ST_EOF; } break; case ST_SIZE_WORD: switch(ft->info.encoding) { case ST_ENCODING_SIGN2: write_buf = st_sw_write_buf; break; case ST_ENCODING_UNSIGNED: write_buf = st_uw_write_buf; break; default: st_fail_errno(ft,ST_EFMT,"Do not support this encoding for this data size"); return ST_EOF; } break; default: st_fail_errno(ft,ST_EFMT,"Do not support this data size for this handler"); return ST_EOF; } /* Prevent overflow */ if (nsamp > alsa->buf_size/ft->info.size) nsamp = (alsa->buf_size/ft->info.size); write_buf(alsa->buf, buf, nsamp, ft->swap); if ((len = snd_pcm_writei(alsa->pcm_handle, alsa->buf, nsamp)) != nsamp) { snd_pcm_prepare(alsa->pcm_handle); fprintf(stderr, "Report me! Buffer underrun thats not dealt with\n"); } return len; } int st_alsastopwrite(ft) ft_t ft; { alsa_priv_t alsa = (alsa_priv_t)ft->priv; snd_pcm_drain(alsa->pcm_handle); snd_pcm_close(alsa->pcm_handle); free(alsa->buf); return ST_SUCCESS; } #define EMSGFMT "ALSA driver does not support %s %s output" static int get_format(ft_t ft, snd_pcm_format_mask_t *fmask, int *fmt) { if (ft->info.size == -1) ft->info.size = ST_SIZE_WORD; if (ft->info.encoding == -1) { if (ft->info.size == ST_SIZE_WORD) ft->info.encoding = ST_ENCODING_SIGN2; else ft->info.encoding = ST_ENCODING_UNSIGNED; } if (ft->info.size != ST_SIZE_WORD && ft->info.size != ST_SIZE_BYTE) { st_warn("ALSA driver only supports byte and word samples. Changing to word.\n"); ft->info.size = ST_SIZE_WORD; } if (ft->info.encoding != ST_ENCODING_SIGN2 && ft->info.encoding != ST_ENCODING_UNSIGNED) { if (ft->info.size == ST_SIZE_WORD) { st_warn("ALSA driver only supports signed and unsigned samples. Changing to signed.\n"); ft->info.encoding = ST_ENCODING_SIGN2; } else { st_warn("ALSA driver only supports signed and unsigned samples. Changing to unsigned.\n"); ft->info.encoding = ST_ENCODING_UNSIGNED; } } /* Some hardware only wants to work with 8-bit or 16-bit data */ if (ft->info.size == ST_SIZE_BYTE) { if (!(snd_pcm_format_mask_test(fmask, SND_PCM_FORMAT_U8)) && !(snd_pcm_format_mask_test(fmask, SND_PCM_FORMAT_S8))) { st_report("ALSA driver doesn't supported byte samples. Changing to words."); ft->info.size = ST_SIZE_WORD; } } else if (ft->info.size == ST_SIZE_WORD) { if (!(snd_pcm_format_mask_test(fmask, SND_PCM_FORMAT_U16)) && !(snd_pcm_format_mask_test(fmask, SND_PCM_FORMAT_S16))) { st_report("ALSA driver doesn't supported word samples. Changing to bytes."); ft->info.size = ST_SIZE_BYTE; } } else { if ((snd_pcm_format_mask_test(fmask, SND_PCM_FORMAT_U16)) || (snd_pcm_format_mask_test(fmask, SND_PCM_FORMAT_S16))) { st_report("ALSA driver doesn't supported %s samples. Changing to words.", st_sizes_str[(unsigned char)ft->info.size]); ft->info.size = ST_SIZE_WORD; } else { st_report("ALSA driver doesn't supported %s samples. Changing to bytes.", st_sizes_str[(unsigned char)ft->info.size]); ft->info.size = ST_SIZE_BYTE; } } if (ft->info.size == ST_SIZE_BYTE) { switch (ft->info.encoding) { case ST_ENCODING_SIGN2: if (!(snd_pcm_format_mask_test(fmask, SND_PCM_FORMAT_S8))) { st_report("ALSA driver doesn't supported signed byte samples. Changing to unsigned bytes."); ft->info.encoding = ST_ENCODING_UNSIGNED; } break; case ST_ENCODING_UNSIGNED: if (!(snd_pcm_format_mask_test(fmask, SND_PCM_FORMAT_U8))) { st_report("ALSA driver doesn't supported unsigned byte samples. Changing to signed bytes."); ft->info.encoding = ST_ENCODING_SIGN2; } break; } switch (ft->info.encoding) { case ST_ENCODING_SIGN2: if (!(snd_pcm_format_mask_test(fmask, SND_PCM_FORMAT_S8))) { st_fail_errno(ft,ST_EFMT,"ALSA driver does not support signed byte samples"); return ST_EOF; } *fmt = SND_PCM_FORMAT_S8; break; case ST_ENCODING_UNSIGNED: if (!(snd_pcm_format_mask_test(fmask, SND_PCM_FORMAT_U8))) { st_fail_errno(ft,ST_EFMT,"ALSA driver does not support unsigned byte samples"); return ST_EOF; } *fmt = SND_PCM_FORMAT_U8; break; } } else if (ft->info.size == ST_SIZE_WORD) { switch (ft->info.encoding) { case ST_ENCODING_SIGN2: if (!(snd_pcm_format_mask_test(fmask, SND_PCM_FORMAT_S16))) { st_report("ALSA driver does not support signed word samples. Changing to unsigned words."); ft->info.encoding = ST_ENCODING_UNSIGNED; } break; case ST_ENCODING_UNSIGNED: if (!(snd_pcm_format_mask_test(fmask, SND_PCM_FORMAT_U16))) { st_report("ALSA driver does not support unsigned word samples. Changing to signed words."); ft->info.encoding = ST_ENCODING_SIGN2; } break; } switch (ft->info.encoding) { case ST_ENCODING_SIGN2: if (!(snd_pcm_format_mask_test(fmask, SND_PCM_FORMAT_S16))) { st_fail_errno(ft,ST_EFMT,"ALSA driver does not support signed word samples"); return ST_EOF; } *fmt = SND_PCM_FORMAT_S16_LE; break; case ST_ENCODING_UNSIGNED: if (!(snd_pcm_format_mask_test(fmask, SND_PCM_FORMAT_U16))) { st_fail_errno(ft,ST_EFMT,"ALSA driver does not support unsigned word samples"); return ST_EOF; } *fmt = SND_PCM_FORMAT_U16_LE; break; } } else { st_fail_errno(ft,ST_EFMT,EMSGFMT,st_encodings_str[(unsigned char)ft->info.encoding], st_sizes_str[(unsigned char)ft->info.size]); return ST_EOF; } return 0; } #endif /* HAVE_ALSA */