/* * Driver for Advanced Linux Sound Architecture, http://alsa.jcu.cz * * Code by Anders Semb Hermansen * Cleanups by Jaroslav Kysela * Ville Syrjala * ALSA 0.9.0 API by Pete Zaitcev * * You can use -a :[,]... * For example: mpg123 -a hw:0,0 aaa.mpg * mpg123 -a guspnp:1 aaa.mpg * * This file comes under GPL license. */ #include "mpg123.h" #include #include #include static int audio_set_playback_params(struct audio_info_struct *ai); int audio_open(struct audio_info_struct *ai) { int err; char *device; if(!ai) return -1; if(ai->device) { /* parse ALSA device name */ device = ai->device; } else { device = "hw:0,0"; } if ((err = snd_pcm_open(&ai->handle, device, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { fprintf(stderr, "open failed for %s: %s\n", device, snd_strerror(err)); /* * An exit is pretty disgusting, for a library. * We are trying to prevent perror() in mpg123 from * using a bogus errno. */ exit(1); } if ((err = snd_pcm_hw_params_malloc(&ai->params)) < 0) { fprintf(stderr, "params malloc failed: %s\n", snd_strerror(err)); snd_pcm_close(ai->handle); return -1; } audio_set_playback_params(ai); /* Probably not really needed. */ return 0; } /* * ALSA's setting model is a phantasmagoric nightmare. It has no notion of a * current setting, but only a notion of a current set of settings. The only way * to set rate and format is to start with a set which includes all settings, * then narrow it down to the settings you need. */ static int audio_set_playback_params(struct audio_info_struct *ai) { snd_pcm_hw_params_t *pp = ai->params; int err; snd_pcm_format_t format; unsigned int buffer_time; /* * First, obtain the complete set. */ if ((err = snd_pcm_hw_params_any(ai->handle, pp)) < 0) { fprintf(stderr, "snd_pcm_hw_params_any failed: %s\n", snd_strerror(err)); return -1; } /* * Narrorow access (not settable) */ err = snd_pcm_hw_params_set_access(ai->handle, pp, SND_PCM_ACCESS_RW_INTERLEAVED); if (err < 0) { fprintf(stderr, "snd_pcm_hw_params_set_access failed: %s\n", snd_strerror(err)); return -1; } /* * Narrow the set with mpg123 settables: channels, format, rate. */ if (ai->channels >= 1) { err = snd_pcm_hw_params_set_channels(ai->handle, pp, ai->channels); if (err < 0) { fprintf(stderr, "snd_pcm_hw_params_set_channels failed: %s\n", snd_strerror(err)); return -1; } } switch(ai->format) { case AUDIO_FORMAT_SIGNED_16: default: format = SND_PCM_FORMAT_S16; break; case AUDIO_FORMAT_UNSIGNED_8: format = SND_PCM_FORMAT_U8; break; case AUDIO_FORMAT_SIGNED_8: format = SND_PCM_FORMAT_S8; break; case AUDIO_FORMAT_ULAW_8: format = SND_PCM_FORMAT_MU_LAW; break; case AUDIO_FORMAT_ALAW_8: format = SND_PCM_FORMAT_A_LAW; break; case AUDIO_FORMAT_UNSIGNED_16: format = SND_PCM_FORMAT_U16; break; } err = snd_pcm_hw_params_set_format(ai->handle, pp, format); if (err < 0) { fprintf(stderr, "snd_pcm_hw_params_set_format failed: %s\n", snd_strerror(err)); return -1; } if (ai->rate >= 0) { err = snd_pcm_hw_params_set_rate(ai->handle, pp, ai->rate, 0); if (err < 0) { fprintf(stderr, "snd_pcm_hw_params_set_rate failed: %s\n", snd_strerror(err)); return -1; } } /* * Default in ALSA is way too interactive, skips like crazy. */ buffer_time = 500000; /* Half second */ if ((err = snd_pcm_hw_params_set_buffer_time_near(ai->handle, pp, &buffer_time, 0)) < 0) { fprintf(stderr, "snd_pcm_hw_params_set_buffer_time_near failed: %s\n", snd_strerror(err)); } /* * Finally, commit settings to the hardware. */ if ((err = snd_pcm_hw_params(ai->handle, pp)) < 0) { fprintf(stderr, "snd_pcm_hw_params failed: %s\n", snd_strerror(err)); /* Not fatal for partial settings at initialization */ return 0; } return 0; } int audio_reset_parameters(struct audio_info_struct *ai) { audio_set_playback_params(ai); return 0; } int audio_rate_best_match(struct audio_info_struct *ai) { return 0; } int audio_set_rate(struct audio_info_struct *ai) { if(!ai || ai->rate < 0) return -1; return audio_set_playback_params(ai); } int audio_set_channels(struct audio_info_struct *ai) { if(ai->channels < 0) return 0; return audio_set_playback_params(ai); } int audio_set_format(struct audio_info_struct *ai) { if(ai->format == -1) return 0; return audio_set_playback_params(ai); } int audio_get_formats(struct audio_info_struct *ai) { snd_pcm_format_mask_t *mask; int i; int fmt = -1; static int fmts[] = { AUDIO_FORMAT_SIGNED_16, AUDIO_FORMAT_UNSIGNED_16, AUDIO_FORMAT_UNSIGNED_8, AUDIO_FORMAT_SIGNED_8, AUDIO_FORMAT_ULAW_8, AUDIO_FORMAT_ALAW_8 }; static int afmts[] = { SND_PCM_FORMAT_S16, SND_PCM_FORMAT_U16, SND_PCM_FORMAT_U8, SND_PCM_FORMAT_S8, SND_PCM_FORMAT_MU_LAW, SND_PCM_FORMAT_A_LAW }; snd_pcm_format_mask_alloca(&mask); snd_pcm_hw_params_any(ai->handle, ai->params); snd_pcm_hw_params_get_format_mask(ai->params, mask); for (i = 0; i < 6; i++) { if (snd_pcm_format_mask_test(mask, afmts[i])) { if (fmt == -1) fmt = 0; fmt |= fmts[i]; } } return fmt; } int audio_play_samples(struct audio_info_struct *ai,unsigned char *buf,int len) { int ret, err; unsigned int factor; factor = 0; if (ai->channels != 1) factor += 1; if (ai->format & AUDIO_FORMAT_MASK) factor += 1; ret = 0; while (len > 0) { if ((err = snd_pcm_writei(ai->handle, buf, len >> factor)) < 0) { if (err == -EPIPE) { snd_pcm_prepare(ai->handle); return ret; } else if (err == -EAGAIN) { err = 0; } else { fprintf(stderr, "snd_pcm_writei failed: %s\n", snd_strerror(ret)); return err; } } err <<= factor; ret += err; len -= err; buf += err; } return ret; } int audio_close(struct audio_info_struct *ai) { int ret; ret = snd_pcm_close(ai->handle); snd_pcm_hw_params_free(ai->params); return ret; }