Merge pull request #130 from chme/laudio

[laudio] Make output type for local audio configurable
This commit is contained in:
ejurgensen 2015-04-16 17:56:01 +02:00
commit 76a638a277
9 changed files with 450 additions and 51 deletions

View File

@ -73,19 +73,21 @@ AC_ARG_ENABLE(lastfm, AS_HELP_STRING([--enable-lastfm], [enable LastFM support (
case "$host" in
*-*-linux-*)
use_alsa=true
use_oss4=false
;;
*-*-kfreebsd*-*|*-*-freebsd*)
use_alsa=false
use_oss4=true
;;
esac
AC_ARG_WITH(oss4, AS_HELP_STRING([--with-oss4], [use OSS4 (default Linux=no, FreeBSD=yes)]),
use_oss4=true;
AS_IF([test "x$with_oss4" = "xyes"],[use_oss4=true],[use_oss4=false]);
)
AC_ARG_WITH(alsa, AS_HELP_STRING([--with-alsa], [use ALSA (default Linux=yes, FreeBSD=no)]),
use_oss4=false;
AS_IF([test "x$with_alsa" = "xyes"],[use_alsa=true],[use_alsa=false]);
)
AC_ARG_ENABLE(mpd, AS_HELP_STRING([--enable-mpd], [enable MPD client protocol support (default=no)]),
@ -98,7 +100,7 @@ AM_CONDITIONAL(COND_ITUNES, test x$use_itunes = xtrue)
AM_CONDITIONAL(COND_SPOTIFY, test x$use_spotify = xtrue)
AM_CONDITIONAL(COND_LASTFM, test x$use_lastfm = xtrue)
AM_CONDITIONAL(COND_OSS4, test x$use_oss4 = xtrue)
AM_CONDITIONAL(COND_ALSA, test x$use_oss4 != xtrue)
AM_CONDITIONAL(COND_ALSA, test x$use_alsa = xtrue)
AM_CONDITIONAL(COND_MPD, test x$use_mpd = xtrue)
dnl Checks for libraries.
@ -218,11 +220,15 @@ if test x$use_lastfm = xtrue; then
AC_DEFINE(HAVE_MXML_GETOPAQUE, 1, [Define to 1 if your mxml has mxmlGetOpaque.]))
fi
if test x$use_oss4 != xtrue; then
if test x$use_alsa = xtrue; then
PKG_CHECK_MODULES(ALSA, [ alsa ])
AC_DEFINE(LAUDIO_USE_ALSA, 1, [define if local audio output uses ALSA])
else
CPPFLAGS="${CPPFLAGS} -DALSA"
fi
if test x$use_oss4 = xtrue; then
AC_CHECK_HEADER(sys/soundcard.h, , AC_MSG_ERROR([sys/soundcard.h not found]))
CPPFLAGS="${CPPFLAGS} -DOSS4"
fi
case "$host" in

View File

@ -140,6 +140,9 @@ audio {
# Name - used in the speaker list in Remote
nickname = "Computer"
# Type of the output (alsa, oss4, dummy)
# type = "alsa"
# Audio device name for local audio output
# card = "default"

View File

@ -115,7 +115,9 @@ forked_daapd_SOURCES = main.c \
daap_query.c daap_query.h \
player.c player.h \
worker.c worker.h \
$(ALSA_SRC) $(OSS4_SRC) laudio.h \
$(ALSA_SRC) $(OSS4_SRC) \
laudio_dummy.c \
laudio.c laudio.h \
raop.c raop.h \
$(RTSP_SRC) \
scan-wma.c \

View File

@ -92,6 +92,7 @@ static cfg_opt_t sec_library[] =
static cfg_opt_t sec_audio[] =
{
CFG_STR("nickname", "Computer", CFGF_NONE),
CFG_STR("type", NULL, CFGF_NONE),
#ifdef __linux__
CFG_STR("card", "default", CFGF_NONE),
#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)

155
src/laudio.c Normal file
View File

@ -0,0 +1,155 @@
/*
* Copyright (C) 2015 Christian Meffert <christian.meffert@googlemail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdint.h>
#include <inttypes.h>
#include "conffile.h"
#include "logger.h"
#include "player.h"
#include "laudio.h"
#ifdef ALSA
extern audio_output audio_alsa;
#endif
#ifdef OSS4
extern audio_output audio_oss4;
#endif
extern audio_output audio_dummy;
static audio_output *outputs[] = {
#ifdef ALSA
&audio_alsa,
#endif
#ifdef OSS4
&audio_oss4,
#endif
&audio_dummy,
NULL
};
static audio_output *output;
struct pcm_packet
{
uint8_t samples[STOB(AIRTUNES_V2_PACKET_SAMPLES)];
uint64_t rtptime;
size_t offset;
struct pcm_packet *next;
};
void
laudio_write(uint8_t *buf, uint64_t rtptime)
{
output->write(buf, rtptime);
}
uint64_t
laudio_get_pos(void)
{
return output->pos();
}
void
laudio_set_volume(int vol)
{
output->volume(vol);
}
int
laudio_start(uint64_t cur_pos, uint64_t next_pkt)
{
return output->start(cur_pos, next_pkt);
}
void
laudio_stop(void)
{
output->stop();
}
int
laudio_open(void)
{
return output->open();
}
void
laudio_close(void)
{
output->close();
}
int
laudio_init(laudio_status_cb cb)
{
cfg_t *cfg_audio;
char *type;
int i;
cfg_audio = cfg_getsec(cfg, "audio");
type = cfg_getstr(cfg_audio, "type");
output = NULL;
if (type)
{
DPRINTF(E_DBG, L_LAUDIO, "Searching for local audio output: '%s'\n", type);
for (i = 0; outputs[i]; i++)
{
if (0 == strcmp(type, outputs[i]->name))
{
output = outputs[i];
}
}
if (!output)
DPRINTF(E_WARN, L_LAUDIO, "No local audio output '%s' available, falling back to default output\n", type);
}
if (!output)
{
output = outputs[0];
}
DPRINTF(E_INFO, L_LAUDIO, "Local audio output: '%s'\n", output->name);
return output->init(cb, cfg_audio);
}
void
laudio_deinit(void)
{
output->deinit();
}

View File

@ -17,6 +17,39 @@ enum laudio_state
typedef void (*laudio_status_cb)(enum laudio_state status);
typedef struct
{
// Identifier of th audio output
char *name;
// Initialization function called during startup
int (*init)(laudio_status_cb cb, cfg_t *cfg_audio);
// Deinitialization function called at shutdown
void (*deinit)(void);
// Function to open the output called at playback start or speaker activiation
int (*open)(void);
// Function called after opening the output (during playback start or speaker activiation
int (*start)(uint64_t cur_pos, uint64_t next_pkt);
// block of samples
void (*write)(uint8_t *buf, uint64_t rtptime);
// Stopping audio playback
void (*stop)(void);
// Closes the output
void (*close)(void);
// Returns the rtptime of the packet thats is currently playing
uint64_t (*pos)();
// Sets the volum for the output
void (*volume)(int vol);
} audio_output;
void
laudio_write(uint8_t *buf, uint64_t rtptime);

View File

@ -76,7 +76,7 @@ update_status(enum laudio_state status)
}
static int
laudio_xrun_recover(int err)
laudio_alsa_xrun_recover(int err)
{
int ret;
@ -127,7 +127,7 @@ laudio_xrun_recover(int err)
}
static int
laudio_set_start_threshold(snd_pcm_uframes_t threshold)
laudio_alsa_set_start_threshold(snd_pcm_uframes_t threshold)
{
snd_pcm_sw_params_t *sw_params;
int ret;
@ -172,12 +172,13 @@ laudio_set_start_threshold(snd_pcm_uframes_t threshold)
return -1;
}
void
laudio_write(uint8_t *buf, uint64_t rtptime)
static void
laudio_alsa_write(uint8_t *buf, uint64_t rtptime)
{
struct pcm_packet *pkt;
snd_pcm_sframes_t nsamp;
int ret;
snd_pcm_sframes_t delay;
pkt = (struct pcm_packet *)malloc(sizeof(struct pcm_packet));
if (!pkt)
@ -214,7 +215,7 @@ laudio_write(uint8_t *buf, uint64_t rtptime)
else if ((pcm_status != LAUDIO_RUNNING) && (pcm_pos + pcm_buf_threshold >= pcm_start_pos))
{
/* Kill threshold */
ret = laudio_set_start_threshold(0);
ret = laudio_alsa_set_start_threshold(0);
if (ret < 0)
DPRINTF(E_WARN, L_LAUDIO, "Couldn't set PCM start threshold to 0 for output start\n");
@ -223,11 +224,23 @@ laudio_write(uint8_t *buf, uint64_t rtptime)
pkt = pcm_pkt_head;
ret = snd_pcm_delay(hdl, &delay);
DPRINTF(E_SPAM, L_LAUDIO, "Current delay %" PRIu64 "\n", delay);
snd_pcm_state_t pcmstate = snd_pcm_state(hdl);
if (pcmstate == SND_PCM_STATE_RUNNING)
{
DPRINTF(E_SPAM, L_LAUDIO, "PCM-state == RUNNING\n");
}
else if (pcmstate == SND_PCM_STATE_PREPARED)
{
DPRINTF(E_SPAM, L_LAUDIO, "PCM-state == PREPARED\n");
}
while (pkt)
{
if (pcm_recovery)
{
ret = laudio_xrun_recover(0);
ret = laudio_alsa_xrun_recover(0);
if ((ret == 2) && (pcm_recovery < 10))
return;
else
@ -243,7 +256,7 @@ laudio_write(uint8_t *buf, uint64_t rtptime)
nsamp = snd_pcm_writei(hdl, pkt->samples + pkt->offset, BTOS(sizeof(pkt->samples) - pkt->offset));
if ((nsamp == -EPIPE) || (nsamp == -ESTRPIPE))
{
ret = laudio_xrun_recover(nsamp);
ret = laudio_alsa_xrun_recover(nsamp);
if ((ret < 0) || (ret == 1))
{
if (ret < 0)
@ -287,8 +300,8 @@ laudio_write(uint8_t *buf, uint64_t rtptime)
}
}
uint64_t
laudio_get_pos(void)
static uint64_t
laudio_alsa_get_pos(void)
{
snd_pcm_sframes_t delay;
int ret;
@ -307,8 +320,8 @@ laudio_get_pos(void)
return pcm_pos - delay;
}
void
laudio_set_volume(int vol)
static void
laudio_alsa_set_volume(int vol)
{
int pcm_vol;
@ -340,8 +353,8 @@ laudio_set_volume(int vol)
snd_mixer_selem_set_playback_volume_all(vol_elem, pcm_vol);
}
int
laudio_start(uint64_t cur_pos, uint64_t next_pkt)
static int
laudio_alsa_start(uint64_t cur_pos, uint64_t next_pkt)
{
int ret;
@ -353,6 +366,7 @@ laudio_start(uint64_t cur_pos, uint64_t next_pkt)
return -1;
}
DPRINTF(E_DBG, L_LAUDIO, "Start local audio curpos %" PRIu64 ", next_pkt %" PRIu64 "\n", cur_pos, next_pkt);
DPRINTF(E_DBG, L_LAUDIO, "PCM will start after %d samples (%d packets)\n", pcm_buf_threshold, pcm_buf_threshold / AIRTUNES_V2_PACKET_SAMPLES);
/* Make pcm_pos the rtptime of the packet containing cur_pos */
@ -373,7 +387,7 @@ laudio_start(uint64_t cur_pos, uint64_t next_pkt)
pcm_last_error = 0;
pcm_recovery = 0;
ret = laudio_set_start_threshold(pcm_buf_threshold);
ret = laudio_alsa_set_start_threshold(pcm_buf_threshold);
if (ret < 0)
{
DPRINTF(E_LOG, L_LAUDIO, "Could not set PCM start threshold for local audio start\n");
@ -386,8 +400,8 @@ laudio_start(uint64_t cur_pos, uint64_t next_pkt)
return 0;
}
void
laudio_stop(void)
static void
laudio_alsa_stop(void)
{
struct pcm_packet *pkt;
@ -509,8 +523,8 @@ mixer_open(void)
return -1;
}
int
laudio_open(void)
static int
laudio_alsa_open(void)
{
snd_pcm_hw_params_t *hw_params;
snd_pcm_uframes_t bufsize;
@ -633,8 +647,8 @@ laudio_open(void)
return -1;
}
void
laudio_close(void)
static void
laudio_alsa_close(void)
{
struct pcm_packet *pkt;
@ -664,15 +678,15 @@ laudio_close(void)
}
int
laudio_init(laudio_status_cb cb)
static int
laudio_alsa_init(laudio_status_cb cb, cfg_t *cfg_audio)
{
snd_lib_error_set_handler(logger_alsa);
status_cb = cb;
card_name = cfg_getstr(cfg_getsec(cfg, "audio"), "card");
mixer_name = cfg_getstr(cfg_getsec(cfg, "audio"), "mixer");
card_name = cfg_getstr(cfg_audio, "card");
mixer_name = cfg_getstr(cfg_audio, "mixer");
hdl = NULL;
mixer_hdl = NULL;
@ -681,8 +695,21 @@ laudio_init(laudio_status_cb cb)
return 0;
}
void
laudio_deinit(void)
static void
laudio_alsa_deinit(void)
{
snd_lib_error_set_handler(NULL);
}
audio_output audio_alsa = {
.name = "alsa",
.init = &laudio_alsa_init,
.deinit = &laudio_alsa_deinit,
.start = &laudio_alsa_start,
.stop = &laudio_alsa_stop,
.open = &laudio_alsa_open,
.close = &laudio_alsa_close,
.pos = &laudio_alsa_get_pos,
.write = &laudio_alsa_write,
.volume = &laudio_alsa_set_volume,
};

158
src/laudio_dummy.c Normal file
View File

@ -0,0 +1,158 @@
/*
* Copyright (C) 2015 Christian Meffert <christian.meffert@googlemail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdint.h>
#include <inttypes.h>
#include "conffile.h"
#include "logger.h"
#include "misc.h"
#include "player.h"
#include "laudio.h"
static enum laudio_state pcm_status;
static laudio_status_cb status_cb;
static struct timespec timer_res;
static struct timespec ts;
static uint64_t pcmpos;
static uint64_t
laudio_dummy_get_pos(void)
{
struct timespec cur_timer_res;
struct timespec cur_ts;
uint64_t delta;
int ret;
ret = clock_gettime_with_res(CLOCK_MONOTONIC, &cur_ts, &cur_timer_res);
if (ret < 0)
{
DPRINTF(E_LOG, L_LAUDIO, "Couldn't get clock: %s\n", strerror(errno));
return -1;
}
delta = (cur_ts.tv_sec - ts.tv_sec) * 1000000 + (cur_ts.tv_nsec - ts.tv_nsec) / 1000;
delta = (delta * 44100) / 1000000;
DPRINTF(E_DBG, L_LAUDIO, "Start: %" PRIu64 ", Pos: %" PRIu64 "\n", pcmpos, delta);
return (pcmpos + delta);
}
static void
laudio_dummy_write(uint8_t *buf, uint64_t rtptime)
{
uint64_t pos;
pos = laudio_dummy_get_pos();
if (pcm_status != LAUDIO_RUNNING && pos > (pcmpos + 88200))
{
pcm_status = LAUDIO_RUNNING;
status_cb(LAUDIO_RUNNING);
}
}
static void
laudio_dummy_set_volume(int vol)
{
}
static int
laudio_dummy_start(uint64_t cur_pos, uint64_t next_pkt)
{
int ret;
ret = clock_gettime_with_res(CLOCK_MONOTONIC, &ts, &timer_res);
if (ret < 0)
{
DPRINTF(E_LOG, L_LAUDIO, "Couldn't get current clock: %s\n", strerror(errno));
return -1;
}
pcmpos = cur_pos;
pcm_status = LAUDIO_STARTED;
status_cb(LAUDIO_STARTED);
return 0;
}
static void
laudio_dummy_stop(void)
{
pcm_status = LAUDIO_STOPPING;
status_cb(LAUDIO_STOPPING);
pcm_status = LAUDIO_OPEN;
status_cb(LAUDIO_OPEN);
}
static int
laudio_dummy_open(void)
{
pcm_status = LAUDIO_OPEN;
status_cb(LAUDIO_OPEN);
return 0;
}
static void
laudio_dummy_close(void)
{
pcm_status = LAUDIO_CLOSED;
status_cb(LAUDIO_CLOSED);
}
static int
laudio_dummy_init(laudio_status_cb cb, cfg_t *cfg_audio)
{
status_cb = cb;
return 0;
}
static void
laudio_dummy_deinit(void)
{
}
audio_output audio_dummy = {
.name = "dummy",
.init = &laudio_dummy_init,
.deinit = &laudio_dummy_deinit,
.start = &laudio_dummy_start,
.stop = &laudio_dummy_stop,
.open = &laudio_dummy_open,
.close = &laudio_dummy_close,
.pos = &laudio_dummy_get_pos,
.write = &laudio_dummy_write,
.volume = &laudio_dummy_set_volume,
};

View File

@ -73,8 +73,8 @@ update_status(enum laudio_state status)
status_cb(status);
}
void
laudio_write(uint8_t *buf, uint64_t rtptime)
static void
laudio_oss4_write(uint8_t *buf, uint64_t rtptime)
{
struct pcm_packet *pkt;
int scratch;
@ -175,8 +175,8 @@ laudio_write(uint8_t *buf, uint64_t rtptime)
}
}
uint64_t
laudio_get_pos(void)
static uint64_t
laudio_oss4_get_pos(void)
{
int delay;
int ret;
@ -192,8 +192,8 @@ laudio_get_pos(void)
return pcm_pos - BTOS(delay);
}
void
laudio_set_volume(int vol)
static void
laudio_oss4_set_volume(int vol)
{
int oss_vol;
int ret;
@ -212,8 +212,8 @@ laudio_set_volume(int vol)
DPRINTF(E_DBG, L_LAUDIO, "Setting PCM volume to %d (real: %d)\n", vol, (oss_vol & 0xff));
}
int
laudio_start(uint64_t cur_pos, uint64_t next_pkt)
static int
laudio_oss4_start(uint64_t cur_pos, uint64_t next_pkt)
{
int scratch;
int ret;
@ -251,8 +251,8 @@ laudio_start(uint64_t cur_pos, uint64_t next_pkt)
return 0;
}
void
laudio_stop(void)
static void
laudio_oss4_stop(void)
{
struct pcm_packet *pkt;
int ret;
@ -276,8 +276,8 @@ laudio_stop(void)
update_status(LAUDIO_OPEN);
}
int
laudio_open(void)
static int
laudio_oss4_open(void)
{
audio_buf_info bi;
oss_sysinfo si;
@ -369,8 +369,8 @@ laudio_open(void)
return -1;
}
void
laudio_close(void)
static void
laudio_oss4_close(void)
{
struct pcm_packet *pkt;
int ret;
@ -396,18 +396,32 @@ laudio_close(void)
}
int
laudio_init(laudio_status_cb cb)
static int
laudio_oss4_init(laudio_status_cb cb, cfg_t *cfg_audio)
{
status_cb = cb;
card_name = cfg_getstr(cfg_getsec(cfg, "audio"), "card");
card_name = cfg_getstr(cfg_audio, "card");
return 0;
}
void
laudio_deinit(void)
static void
laudio_oss4_deinit(void)
{
/* EMPTY */
}
audio_output audio_oss4 = {
.name = "oss4",
.init = &laudio_oss4_init,
.deinit = &laudio_oss4_deinit,
.start = &laudio_oss4_start,
.stop = &laudio_oss4_stop,
.open = &laudio_oss4_open,
.close = &laudio_oss4_close,
.pos = &laudio_oss4_get_pos,
.write = &laudio_oss4_write,
.volume = &laudio_oss4_set_volume,
};