From 95035f33895b3a4edc8583daf133dd89f82cfdcf Mon Sep 17 00:00:00 2001 From: Muffinman Date: Thu, 20 Jun 2013 18:13:23 +1000 Subject: [PATCH] Alsa output driver Based on a bit of code of @Skaman and bit of mine, I've added support for mixer and audio Alsa support. --- Makefile | 6 +- audio.c | 6 ++ audio_alsa.c | 195 +++++++++++++++++++++++++++++++++++++++++++++++++++ configure | 10 +++ 4 files changed, 216 insertions(+), 1 deletion(-) create mode 100644 audio_alsa.c diff --git a/Makefile b/Makefile index b33d46be..470fcf20 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,11 @@ SRCS += audio_ao.c endif ifdef CONFIG_PULSE - SRCS += audio_pulse.c +SRCS += audio_pulse.c +endif + +ifdef CONFIG_ALSA +SRCS += audio_alsa.c endif ifdef CONFIG_AVAHI diff --git a/audio.c b/audio.c index f3497e62..1a72cb96 100644 --- a/audio.c +++ b/audio.c @@ -35,9 +35,15 @@ extern audio_output audio_ao; #ifdef CONFIG_PULSE extern audio_output audio_pulse; #endif +#ifdef CONFIG_ALSA +extern audio_output audio_alsa; +#endif extern audio_output audio_dummy, audio_pipe; static audio_output *outputs[] = { +#ifdef CONFIG_ALSA + &audio_alsa, +#endif #ifdef CONFIG_PULSE &audio_pulse, #endif diff --git a/audio_alsa.c b/audio_alsa.c new file mode 100644 index 00000000..5e275621 --- /dev/null +++ b/audio_alsa.c @@ -0,0 +1,195 @@ +/* + * libalsa output driver. This file is part of Shairport. + * Copyright (c) Muffinman, Skaman 2013 + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#define ALSA_PCM_NEW_HW_PARAMS_API + +#include +#include +#include +#include +#include "common.h" +#include "audio.h" + +static void help(void); +static int init(int argc, char **argv); +static void deinit(void); +static void start(int sample_rate); +static void play(short buf[], int samples); +static void stop(void); +static void volume(double vol); + +audio_output audio_alsa = { + .name = "alsa", + .help = &help, + .init = &init, + .deinit = &deinit, + .start = &start, + .stop = &stop, + .play = &play, + .volume = NULL +}; + +static snd_pcm_t *alsa_handle = NULL; +static snd_pcm_hw_params_t *alsa_params = NULL; + +static snd_mixer_t *alsa_mix_handle = NULL; +static snd_mixer_elem_t *alsa_mix_elem = NULL; +static snd_mixer_selem_id_t *alsa_mix_sid = NULL; +static long alsa_mix_minv, alsa_mix_range; + +static char *alsa_out_dev = "default"; +static char *alsa_mix_dev = NULL; +static char *alsa_mix_ctrl = "Master"; +static int alsa_mix_index = 0; + +static void help(void) { + printf(" -d output-device set the output device [default*|...]\n" + " -t mixer-type set the mixer type [software*|hardware]\n" + " -m mixer-device set the mixer device ['output-device'*|...]\n" + " -c mixer-control set the mixer control [Master*|...]\n" + " -i mixer-index set the mixer index [0*|...]\n" + " *) default option\n" + ); +} + +static int init(int argc, char **argv) { + int hardware_mixer = 0; + + optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour + argv--; // so we shift the arguments to satisfy getopt() + argc++; + // some platforms apparently require optreset = 1; - which? + int opt; + while ((opt = getopt(argc, argv, "d:t:m:c:i:")) > 0) { + switch (opt) { + case 'd': + alsa_out_dev = optarg; + break; + case 't': + if (strcmp(optarg, "hardware") == 0) + hardware_mixer = 1; + break; + case 'm': + alsa_mix_dev = optarg; + break; + case 'c': + alsa_mix_ctrl = optarg; + break; + case 'i': + alsa_mix_index = strtol(optarg, NULL, 10); + break; + default: + help(); + die("Invalid audio option -%c specified", opt); + } + } + + if (optind < argc) + die("Invalid audio argument: %s", argv[optind]); + + if (!hardware_mixer) + return 0; + + if (alsa_mix_dev == NULL) + alsa_mix_dev = alsa_out_dev; + audio_alsa.volume = &volume; + + int ret = 0; + long alsa_mix_maxv; + + snd_mixer_selem_id_alloca(&alsa_mix_sid); + snd_mixer_selem_id_set_index(alsa_mix_sid, alsa_mix_index); + snd_mixer_selem_id_set_name(alsa_mix_sid, alsa_mix_ctrl); + + if ((snd_mixer_open(&alsa_mix_handle, 0)) < 0) + die ("Failed to open mixer"); + if ((snd_mixer_attach(alsa_mix_handle, alsa_mix_dev)) < 0) + die ("Failed to attach mixer"); + if ((snd_mixer_selem_register(alsa_mix_handle, NULL, NULL)) < 0) + die ("Failed to register mixer element"); + + ret = snd_mixer_load(alsa_mix_handle); + if (ret < 0) + die ("Failed to load mixer element"); + alsa_mix_elem = snd_mixer_find_selem(alsa_mix_handle, alsa_mix_sid); + if (!alsa_mix_elem) + die ("Failed to find mixer element"); + snd_mixer_selem_get_playback_volume_range (alsa_mix_elem, &alsa_mix_minv, &alsa_mix_maxv); + alsa_mix_range = alsa_mix_maxv - alsa_mix_minv; + + return 0; +} + +static void deinit(void) { + stop(); + if (alsa_mix_handle) { + snd_mixer_close(alsa_mix_handle); + } +} + +static void start(int sample_rate) { + if (sample_rate != 44100) + die("Unexpected sample rate!"); + + int ret, dir = 0; + snd_pcm_uframes_t frames = 64; + ret = snd_pcm_open(&alsa_handle, alsa_out_dev, SND_PCM_STREAM_PLAYBACK, 0); + if (ret < 0) + die("Alsa initialization failed: unable to open pcm device: %s\n", snd_strerror(ret)); + + snd_pcm_hw_params_alloca(&alsa_params); + snd_pcm_hw_params_any(alsa_handle, alsa_params); + snd_pcm_hw_params_set_access(alsa_handle, alsa_params, SND_PCM_ACCESS_RW_INTERLEAVED); + snd_pcm_hw_params_set_format(alsa_handle, alsa_params, SND_PCM_FORMAT_S16); + snd_pcm_hw_params_set_channels(alsa_handle, alsa_params, 2); + snd_pcm_hw_params_set_rate_near(alsa_handle, alsa_params, (unsigned int *)&sample_rate, &dir); + snd_pcm_hw_params_set_period_size_near(alsa_handle, alsa_params, &frames, &dir); + ret = snd_pcm_hw_params(alsa_handle, alsa_params); + if (ret < 0) + die("unable to set hw parameters: %s\n", snd_strerror(ret)); +} + +static void play(short buf[], int samples) { + int err = snd_pcm_writei(alsa_handle, (char*)buf, samples); + if (err < 0) + err = snd_pcm_recover(alsa_handle, err, 0); + if (err < 0) + die("Failed to write to PCM device: %s\n", snd_strerror(err)); +} + +static void stop(void) { + if (alsa_handle) { + snd_pcm_drain(alsa_handle); + snd_pcm_close(alsa_handle); + alsa_handle = NULL; + } +} + +static void volume(double vol) { + long alsa_volume = (vol*alsa_mix_range)+alsa_mix_minv; + if(snd_mixer_selem_set_playback_volume_all(alsa_mix_elem, alsa_volume) != 0) + die ("Failed to set playback volume"); +} diff --git a/configure b/configure index e32793f6..b358c4b9 100755 --- a/configure +++ b/configure @@ -43,6 +43,16 @@ else echo libpulse or its dev package not found fi +if pkg-config alsa 2>/dev/null; then + CFLAGS="${CFLAGS} `pkg-config --cflags alsa`" + LDFLAGS="${LDFLAGS} `pkg-config --libs alsa`" + echo "#define CONFIG_ALSA" >> config.h + echo "CONFIG_ALSA=yes" >> config.mk + echo libalsa found +else + echo libalsa or its dev package not found +fi + if pkg-config avahi-client 2>/dev/null; then CFLAGS="${CFLAGS} `pkg-config --cflags avahi-client`" LDFLAGS="${LDFLAGS} `pkg-config --libs avahi-client`"