[alsa] Add rewritten ALSA to generic outputs interface

This commit is contained in:
ejurgensen 2016-04-04 16:58:07 +02:00
parent f1fb86e7e1
commit c5bb83480d
6 changed files with 992 additions and 20 deletions

View File

@ -26,7 +26,7 @@ MPD_SRC=mpd.c mpd.h
endif endif
if COND_ALSA if COND_ALSA
ALSA_SRC=laudio_alsa.c ALSA_SRC=outputs/alsa.c
endif endif
if COND_OSS4 if COND_OSS4

View File

@ -33,9 +33,6 @@
#include "player.h" #include "player.h"
#include "laudio.h" #include "laudio.h"
#ifdef ALSA
extern audio_output audio_alsa;
#endif
#ifdef OSS4 #ifdef OSS4
extern audio_output audio_oss4; extern audio_output audio_oss4;
#endif #endif
@ -43,9 +40,6 @@ extern audio_output audio_oss4;
extern audio_output audio_dummy; extern audio_output audio_dummy;
static audio_output *outputs[] = { static audio_output *outputs[] = {
#ifdef ALSA
&audio_alsa,
#endif
#ifdef OSS4 #ifdef OSS4
&audio_oss4, &audio_oss4,
#endif #endif

View File

@ -32,14 +32,14 @@
#include "outputs.h" #include "outputs.h"
extern struct output_definition output_raop; extern struct output_definition output_raop;
extern struct output_definition output_streaming;
#ifdef CHROMECAST #ifdef CHROMECAST
extern struct output_definition output_cast; extern struct output_definition output_cast;
#endif #endif
extern struct output_definition output_streaming;
/* TODO
#ifdef ALSA #ifdef ALSA
extern struct output_definition output_alsa; extern struct output_definition output_alsa;
#endif #endif
/* TODO
#ifdef OSS4 #ifdef OSS4
extern struct output_definition output_oss4; extern struct output_definition output_oss4;
#endif #endif
@ -49,14 +49,14 @@ extern struct output_definition output_dummy;
// Must be in sync with enum output_types // Must be in sync with enum output_types
static struct output_definition *outputs[] = { static struct output_definition *outputs[] = {
&output_raop, &output_raop,
&output_streaming,
#ifdef CHROMECAST #ifdef CHROMECAST
&output_cast, &output_cast,
#endif #endif
&output_streaming,
/* TODO
#ifdef ALSA #ifdef ALSA
&output_alsa, &output_alsa,
#endif #endif
/* TODO
#ifdef OSS4 #ifdef OSS4
&output_oss4, &output_oss4,
#endif #endif

View File

@ -14,18 +14,48 @@
* When a device is started the output backend will typically create a session. * When a device is started the output backend will typically create a session.
* This session is only passed around as an opaque object in this interface. * This session is only passed around as an opaque object in this interface.
* *
* Here is the sequence of commands from the player to the outputs, and the
* callback from the output once the command has been executed. Commands marked
* with * may make multiple callbacks if multiple sessions are affected.
* (TODO should callbacks always be deferred?)
*
* PLAYER OUTPUT PLAYER CB
* speaker_activate -> device_start -> device_activate_cb
* -> (if playback) -> playback_start -> device_streaming_cb* (or no cb)
* -> (else if playback not active) -> device_streaming_cb
* -> (fail) -> device_stop -> device_lost_cb
* speaker_activate -> device_probe -> device_probe_cb
* speaker_deactivate -> device_stop -> device_shutdown_cb
* volume_set -> device_volume_set -> device_command_cb
* -> -> device_streaming_cb
* (volume_setrel/abs_speaker is the same)
* playback_start_item -> device_start -> device_restart_cb
* -> (success) -> device_streaming_cb
* -> (fail) -> device_stop -> device_lost_cb
* playback_start_bh -> playback_start -> device_streaming_cb* (or no cb)
* playback_stop -> flush -> device_command_cb*
* -> -> device_streaming_cb*
* -> -> playback_stop -> device_streaming_cb*
* playback_pause -> flush -> device_command_cb*
* -> -> device_streaming_cb*
* -> -> playback_stop -> device_streaming_cb*
* playback_abort -> playback_stop -> device_streaming_cb* (or no cb)
* device_streaming_cb -> device_streaming_cb (re-add)
*
*/ */
// Must be in sync with outputs[] in outputs.c // Must be in sync with outputs[] in outputs.c
enum output_types enum output_types
{ {
OUTPUT_TYPE_RAOP, OUTPUT_TYPE_RAOP,
OUTPUT_TYPE_STREAMING,
#ifdef CHROMECAST #ifdef CHROMECAST
OUTPUT_TYPE_CAST, OUTPUT_TYPE_CAST,
#endif #endif
OUTPUT_TYPE_STREAMING, #ifdef ALSA
/* TODO
OUTPUT_TYPE_ALSA, OUTPUT_TYPE_ALSA,
#endif
/* TODO
OUTPUT_TYPE_OSS, OUTPUT_TYPE_OSS,
OUTPUT_TYPE_DUMMY, OUTPUT_TYPE_DUMMY,
*/ */

934
src/outputs/alsa.c Normal file
View File

@ -0,0 +1,934 @@
/*
* Copyright (C) 2015-2016 Espen Jürgensen <espenjurgensen@gmail.com>
* Copyright (C) 2010 Julien BLACHE <jb@jblache.org>
*
* 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 <event2/event.h>
#include <asoundlib.h>
#include "conffile.h"
#include "logger.h"
#include "player.h"
#include "outputs.h"
#define ALSA_PACKETS_WRITE_MAX 10
#define PACKET_SIZE STOB(AIRTUNES_V2_PACKET_SAMPLES)
// TODO Unglobalise these and add support for multiple sound cards
static char *card_name;
static char *mixer_name;
static snd_pcm_t *hdl;
static snd_mixer_t *mixer_hdl;
static snd_mixer_elem_t *vol_elem;
static long vol_min;
static long vol_max;
#define ALSA_F_STARTED (1 << 15)
enum alsa_state
{
ALSA_STATE_STOPPED = 0,
ALSA_STATE_STARTED = ALSA_F_STARTED,
ALSA_STATE_STREAMING = ALSA_F_STARTED | 0x01,
ALSA_STATE_FAILED = -1,
};
struct alsa_session
{
enum alsa_state state;
char *devname;
uint64_t pos;
uint64_t start_pos;
// An array that will hold the packets we prebuffer. The length of the array
// is prebuf_len (measured in rtp_packets)
uint8_t *prebuf;
uint32_t prebuf_len;
uint32_t prebuf_head;
uint32_t prebuf_tail;
int volume;
struct event *deferredev;
output_status_cb defer_cb;
/* Do not dereference - only passed to the status cb */
struct output_device *device;
struct output_session *output_session;
output_status_cb status_cb;
struct alsa_session *next;
};
/* From player.c */
extern struct event_base *evbase_player;
static struct alsa_session *sessions;
/* Forwards */
static void
defer_cb(int fd, short what, void *arg);
/* ---------------------------- SESSION HANDLING ---------------------------- */
static void
prebuf_free(struct alsa_session *as)
{
if (as->prebuf)
free(as->prebuf);
as->prebuf = NULL;
as->prebuf_len = 0;
as->prebuf_head = 0;
as->prebuf_tail = 0;
}
static void
alsa_session_free(struct alsa_session *as)
{
event_free(as->deferredev);
prebuf_free(as);
free(as->output_session);
free(as);
as = NULL;
}
static void
alsa_session_cleanup(struct alsa_session *as)
{
struct alsa_session *s;
if (as == sessions)
sessions = sessions->next;
else
{
for (s = sessions; s && (s->next != as); s = s->next)
; /* EMPTY */
if (!s)
DPRINTF(E_WARN, L_LAUDIO, "WARNING: struct alsa_session not found in list; BUG!\n");
else
s->next = as->next;
}
alsa_session_free(as);
}
static struct alsa_session *
alsa_session_make(struct output_device *device, output_status_cb cb)
{
struct output_session *os;
struct alsa_session *as;
os = calloc(1, sizeof(struct output_session));
as = calloc(1, sizeof(struct alsa_session));
if (!os || !as)
{
DPRINTF(E_LOG, L_LAUDIO, "Out of memory for ALSA session\n");
return NULL;
}
as->deferredev = evtimer_new(evbase_player, defer_cb, as);
if (!as->deferredev)
{
DPRINTF(E_LOG, L_LAUDIO, "Out of memory for ALSA deferred event\n");
free(os);
free(as);
return NULL;
}
os->session = as;
os->type = device->type;
as->output_session = os;
as->state = ALSA_STATE_STOPPED;
as->device = device;
as->status_cb = cb;
as->volume = device->volume;
as->devname = card_name;
as->next = sessions;
sessions = as;
return as;
}
/* ---------------------------- STATUS HANDLERS ----------------------------- */
// Maps our internal state to the generic output state and then makes a callback
// to the player to tell that state
static void
defer_cb(int fd, short what, void *arg)
{
struct alsa_session *as = arg;
enum output_device_state state;
switch (as->state)
{
case ALSA_STATE_FAILED:
state = OUTPUT_STATE_FAILED;
break;
case ALSA_STATE_STOPPED:
state = OUTPUT_STATE_STOPPED;
break;
case ALSA_STATE_STARTED:
state = OUTPUT_STATE_CONNECTED;
break;
case ALSA_STATE_STREAMING:
state = OUTPUT_STATE_STREAMING;
break;
default:
DPRINTF(E_LOG, L_LAUDIO, "Bug! Unhandled state in alsa_status()\n");
state = OUTPUT_STATE_FAILED;
}
if (as->defer_cb)
as->defer_cb(as->device, as->output_session, state);
if (!(as->state & ALSA_F_STARTED))
alsa_session_cleanup(as);
}
// Note: alsa_states also nukes the session if it is not ALSA_F_STARTED
static void
alsa_status(struct alsa_session *as)
{
as->defer_cb = as->status_cb;
event_active(as->deferredev, 0, 0);
as->status_cb = NULL;
}
/* ------------------------------- MISC HELPERS ----------------------------- */
/*static int
start_threshold_set(snd_pcm_uframes_t threshold)
{
snd_pcm_sw_params_t *sw_params;
int ret;
ret = snd_pcm_sw_params_malloc(&sw_params);
if (ret < 0)
{
DPRINTF(E_LOG, L_LAUDIO, "Could not allocate sw params: %s\n", snd_strerror(ret));
goto out_fail;
}
ret = snd_pcm_sw_params_current(hdl, sw_params);
if (ret < 0)
{
DPRINTF(E_LOG, L_LAUDIO, "Could not retrieve current sw params: %s\n", snd_strerror(ret));
goto out_fail;
}
ret = snd_pcm_sw_params_set_start_threshold(hdl, sw_params, threshold);
if (ret < 0)
{
DPRINTF(E_LOG, L_LAUDIO, "Could not set start threshold: %s\n", snd_strerror(ret));
goto out_fail;
}
ret = snd_pcm_sw_params(hdl, sw_params);
if (ret < 0)
{
DPRINTF(E_LOG, L_LAUDIO, "Could not set sw params: %s\n", snd_strerror(ret));
goto out_fail;
}
return 0;
out_fail:
snd_pcm_sw_params_free(sw_params);
return -1;
}
*/
static int
mixer_open(void)
{
snd_mixer_elem_t *elem;
snd_mixer_elem_t *master;
snd_mixer_elem_t *pcm;
snd_mixer_elem_t *custom;
snd_mixer_selem_id_t *sid;
int ret;
ret = snd_mixer_open(&mixer_hdl, 0);
if (ret < 0)
{
DPRINTF(E_LOG, L_LAUDIO, "Failed to open mixer: %s\n", snd_strerror(ret));
mixer_hdl = NULL;
return -1;
}
ret = snd_mixer_attach(mixer_hdl, card_name);
if (ret < 0)
{
DPRINTF(E_LOG, L_LAUDIO, "Failed to attach mixer: %s\n", snd_strerror(ret));
goto out_close;
}
ret = snd_mixer_selem_register(mixer_hdl, NULL, NULL);
if (ret < 0)
{
DPRINTF(E_LOG, L_LAUDIO, "Failed to register mixer: %s\n", snd_strerror(ret));
goto out_detach;
}
ret = snd_mixer_load(mixer_hdl);
if (ret < 0)
{
DPRINTF(E_LOG, L_LAUDIO, "Failed to load mixer: %s\n", snd_strerror(ret));
goto out_detach;
}
// Grab interesting elements
snd_mixer_selem_id_alloca(&sid);
pcm = NULL;
master = NULL;
custom = NULL;
for (elem = snd_mixer_first_elem(mixer_hdl); elem; elem = snd_mixer_elem_next(elem))
{
snd_mixer_selem_get_id(elem, sid);
if (mixer_name && (strcmp(snd_mixer_selem_id_get_name(sid), mixer_name) == 0))
{
custom = elem;
break;
}
else if (strcmp(snd_mixer_selem_id_get_name(sid), "PCM") == 0)
pcm = elem;
else if (strcmp(snd_mixer_selem_id_get_name(sid), "Master") == 0)
master = elem;
}
if (mixer_name)
{
if (custom)
vol_elem = custom;
else
{
DPRINTF(E_LOG, L_LAUDIO, "Failed to open configured mixer element '%s'\n", mixer_name);
goto out_detach;
}
}
else if (pcm)
vol_elem = pcm;
else if (master)
vol_elem = master;
else
{
DPRINTF(E_LOG, L_LAUDIO, "Failed to open PCM or Master mixer element\n");
goto out_detach;
}
// Get min & max volume
snd_mixer_selem_get_playback_volume_range(vol_elem, &vol_min, &vol_max);
return 0;
out_detach:
snd_mixer_detach(mixer_hdl, card_name);
out_close:
snd_mixer_close(mixer_hdl);
mixer_hdl = NULL;
vol_elem = NULL;
return -1;
}
static int
device_open(void)
{
snd_pcm_hw_params_t *hw_params;
snd_pcm_uframes_t bufsize;
int ret;
hw_params = NULL;
ret = snd_pcm_open(&hdl, card_name, SND_PCM_STREAM_PLAYBACK, 0);
if (ret < 0)
{
DPRINTF(E_LOG, L_LAUDIO, "Could not open playback device: %s\n", snd_strerror(ret));
return -1;
}
// HW params
ret = snd_pcm_hw_params_malloc(&hw_params);
if (ret < 0)
{
DPRINTF(E_LOG, L_LAUDIO, "Could not allocate hw params: %s\n", snd_strerror(ret));
goto out_fail;
}
ret = snd_pcm_hw_params_any(hdl, hw_params);
if (ret < 0)
{
DPRINTF(E_LOG, L_LAUDIO, "Could not retrieve hw params: %s\n", snd_strerror(ret));
goto out_fail;
}
ret = snd_pcm_hw_params_set_access(hdl, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
if (ret < 0)
{
DPRINTF(E_LOG, L_LAUDIO, "Could not set access method: %s\n", snd_strerror(ret));
goto out_fail;
}
ret = snd_pcm_hw_params_set_format(hdl, hw_params, SND_PCM_FORMAT_S16_LE);
if (ret < 0)
{
DPRINTF(E_LOG, L_LAUDIO, "Could not set S16LE format: %s\n", snd_strerror(ret));
goto out_fail;
}
ret = snd_pcm_hw_params_set_channels(hdl, hw_params, 2);
if (ret < 0)
{
DPRINTF(E_LOG, L_LAUDIO, "Could not set stereo output: %s\n", snd_strerror(ret));
goto out_fail;
}
ret = snd_pcm_hw_params_set_rate(hdl, hw_params, 44100, 0);
if (ret < 0)
{
DPRINTF(E_LOG, L_LAUDIO, "Hardware doesn't support 44.1 kHz: %s\n", snd_strerror(ret));
goto out_fail;
}
ret = snd_pcm_hw_params_get_buffer_size_max(hw_params, &bufsize);
if (ret < 0)
{
DPRINTF(E_LOG, L_LAUDIO, "Could not get max buffer size: %s\n", snd_strerror(ret));
goto out_fail;
}
ret = snd_pcm_hw_params_set_buffer_size_max(hdl, hw_params, &bufsize);
if (ret < 0)
{
DPRINTF(E_LOG, L_LAUDIO, "Could not set buffer size to max: %s\n", snd_strerror(ret));
goto out_fail;
}
ret = snd_pcm_hw_params(hdl, hw_params);
if (ret < 0)
{
DPRINTF(E_LOG, L_LAUDIO, "Could not set hw params: %s\n", snd_strerror(ret));
goto out_fail;
}
snd_pcm_hw_params_free(hw_params);
hw_params = NULL;
ret = mixer_open();
if (ret < 0)
{
DPRINTF(E_LOG, L_LAUDIO, "Could not open mixer\n");
goto out_fail;
}
return 0;
out_fail:
if (hw_params)
snd_pcm_hw_params_free(hw_params);
snd_pcm_close(hdl);
hdl = NULL;
return -1;
}
static void
device_close(void)
{
snd_pcm_close(hdl);
hdl = NULL;
if (mixer_hdl)
{
snd_mixer_detach(mixer_hdl, card_name);
snd_mixer_close(mixer_hdl);
mixer_hdl = NULL;
vol_elem = NULL;
}
}
static void
playback_start(struct alsa_session *as, uint64_t pos, uint64_t start_pos)
{
snd_output_t *output;
char *debug_pcm_cfg;
int ret;
ret = snd_pcm_prepare(hdl);
if (ret < 0)
{
DPRINTF(E_LOG, L_LAUDIO, "Could not prepare ALSA device '%s': %s\n", as->devname, snd_strerror(ret));
return;
}
// Clear prebuffer in case start somehow got called twice without a stop in between
prebuf_free(as);
// The difference between pos and start_pos should match the 2 second
// buffer that AirPlay uses. We will not use alsa's buffer for the initial
// buffering, because my sound card's start_threshold is not to be counted on.
// Instead we allocate our own buffer, and when it is time to play we write as
// much as we can to alsa's buffer.
as->prebuf_len = (start_pos - pos) / AIRTUNES_V2_PACKET_SAMPLES + 1;
if (as->prebuf_len > 2*126)
{
DPRINTF(E_LOG, L_LAUDIO, "Sanity check of prebuf_len (%" PRIu32 " packets) failed\n", as->prebuf_len);
return;
}
DPRINTF(E_DBG, L_LAUDIO, "Will prebuffer %d packets\n", as->prebuf_len);
as->prebuf = malloc(as->prebuf_len * PACKET_SIZE);
if (!as->prebuf)
{
DPRINTF(E_LOG, L_LAUDIO, "Out of memory for audio buffer (requested %" PRIu32 " packets)\n", as->prebuf_len);
return;
}
as->pos = pos;
as->start_pos = start_pos;
// Dump PCM config data for E_DBG logging
ret = snd_output_buffer_open(&output);
if (ret == 0)
{
if (snd_pcm_dump_setup(hdl, output) == 0)
{
snd_output_buffer_string(output, &debug_pcm_cfg);
DPRINTF(E_DBG, L_LAUDIO, "Dump of sound device config:\n%s\n", debug_pcm_cfg);
}
snd_output_close(output);
}
as->state = ALSA_STATE_STREAMING;
}
static void
playback_write(struct alsa_session *as, uint8_t *buf, uint64_t rtptime)
{
snd_pcm_sframes_t ret;
snd_pcm_sframes_t avail;
snd_pcm_sframes_t nsamp;
uint8_t *pkt;
int prebuffering;
int prebuf_empty;
int npackets;
prebuffering = (as->pos < as->start_pos);
prebuf_empty = (as->prebuf_head == as->prebuf_tail);
as->pos += AIRTUNES_V2_PACKET_SAMPLES;
// We need to copy to the prebuffer if we are prebuffering OR if the
// prebuffer has not been emptied yet
if (prebuffering || !prebuf_empty)
{
pkt = &as->prebuf[as->prebuf_head * PACKET_SIZE];
memcpy(pkt, buf, PACKET_SIZE);
as->prebuf_head = (as->prebuf_head + 1) % as->prebuf_len;
}
if (prebuffering)
return;
ret = snd_pcm_avail_update(hdl);
if (ret < 0)
goto alsa_error;
avail = ret;
if (avail < AIRTUNES_V2_PACKET_SAMPLES)
return;
// If we have data in prebuf we send as much as we can
if (!prebuf_empty)
{
buf = &as->prebuf[as->prebuf_tail * PACKET_SIZE];
if (as->prebuf_head > as->prebuf_tail)
npackets = as->prebuf_head - as->prebuf_tail;
else
npackets = as->prebuf_len - as->prebuf_tail;
nsamp = npackets * AIRTUNES_V2_PACKET_SAMPLES;
while (nsamp > avail)
{
npackets -= 1;
nsamp -= AIRTUNES_V2_PACKET_SAMPLES;
}
as->prebuf_tail = (as->prebuf_tail + npackets) % as->prebuf_len;
}
else
nsamp = AIRTUNES_V2_PACKET_SAMPLES;
ret = snd_pcm_writei(hdl, buf, nsamp);
if (ret < 0)
goto alsa_error;
else if (ret != nsamp)
DPRINTF(E_WARN, L_LAUDIO, "ALSA partial write detected\n");
return;
alsa_error:
if (ret == -EPIPE)
{
DPRINTF(E_WARN, L_LAUDIO, "ALSA buffer underrun\n");
ret = snd_pcm_prepare(hdl);
if (ret < 0)
{
DPRINTF(E_WARN, L_LAUDIO, "ALSA couldn't recover from underrun: %s\n", snd_strerror(ret));
return;
}
// Fill the prebuf with audio before restarting, so we don't underrun again
as->start_pos = as->pos + AIRTUNES_V2_PACKET_SAMPLES * (as->prebuf_len - 1);
return;
}
DPRINTF(E_LOG, L_LAUDIO, "ALSA write error: %s\n", snd_strerror(ret));
as->state = ALSA_STATE_FAILED;
alsa_status(as);
}
static void
playback_pos_get(uint64_t *pos, uint64_t next_pkt)
{
uint64_t cur_pos;
struct timespec now;
int ret;
ret = player_get_current_pos(&cur_pos, &now, 0);
if (ret < 0)
{
DPRINTF(E_LOG, L_LAUDIO, "Could not get playback position, setting to next_pkt - 2 seconds\n");
cur_pos = next_pkt - 88200;
}
// Make pos the rtptime of the packet containing cur_pos
*pos = next_pkt;
while (*pos > cur_pos)
*pos -= AIRTUNES_V2_PACKET_SAMPLES;
}
/* ------------------ INTERFACE FUNCTIONS CALLED BY OUTPUTS.C --------------- */
static int
alsa_device_start(struct output_device *device, output_status_cb cb, uint64_t rtptime)
{
struct alsa_session *as;
int ret;
as = alsa_session_make(device, cb);
if (!as)
return -1;
ret = device_open();
if (ret < 0)
return -1;
as->state = ALSA_STATE_STARTED;
alsa_status(as);
return 0;
}
static void
alsa_device_stop(struct output_session *session)
{
struct alsa_session *as = session->session;
device_close();
as->state = ALSA_STATE_STOPPED;
alsa_status(as);
}
static int
alsa_device_probe(struct output_device *device, output_status_cb cb)
{
struct alsa_session *as;
int ret;
as = alsa_session_make(device, cb);
if (!as)
return -1;
ret = device_open();
if (ret < 0)
{
alsa_session_cleanup(as);
return -1;
}
device_close();
as->state = ALSA_STATE_STOPPED;
alsa_status(as);
return 0;
}
static int
alsa_device_volume_set(struct output_device *device, output_status_cb cb)
{
struct alsa_session *as;
int pcm_vol;
if (!device->session || !device->session->session)
return 0;
as = device->session->session;
if (!mixer_hdl || !vol_elem)
return -1;
snd_mixer_handle_events(mixer_hdl);
if (!snd_mixer_selem_is_active(vol_elem))
return -1;
switch (device->volume)
{
case 0:
pcm_vol = vol_min;
break;
case 100:
pcm_vol = vol_max;
break;
default:
pcm_vol = vol_min + (device->volume * (vol_max - vol_min)) / 100;
break;
}
DPRINTF(E_DBG, L_LAUDIO, "Setting ALSA volume to %d (%d)\n", pcm_vol, device->volume);
snd_mixer_selem_set_playback_volume_all(vol_elem, pcm_vol);
alsa_status(as);
return 0;
}
static void
alsa_playback_start(uint64_t next_pkt, struct timespec *ts)
{
struct alsa_session *as;
uint64_t pos;
if (!sessions)
return;
playback_pos_get(&pos, next_pkt);
DPRINTF(E_DBG, L_LAUDIO, "Starting ALSA audio (pos %" PRIu64 ", next_pkt %" PRIu64 ")\n", pos, next_pkt);
for (as = sessions; as; as = as->next)
playback_start(as, pos, next_pkt);
}
static void
alsa_playback_stop(void)
{
struct alsa_session *as;
for (as = sessions; as; as = as->next)
{
snd_pcm_drop(hdl);
prebuf_free(as);
as->state = ALSA_STATE_STARTED;
alsa_status(as);
}
}
static void
alsa_write(uint8_t *buf, uint64_t rtptime)
{
struct alsa_session *as;
uint64_t pos;
for (as = sessions; as; as = as->next)
{
if (as->state == ALSA_STATE_STARTED)
{
playback_pos_get(&pos, rtptime);
DPRINTF(E_DBG, L_LAUDIO, "Starting ALSA device '%s' (pos %" PRIu64 ", rtptime %" PRIu64 ")\n", as->devname, pos, rtptime);
playback_start(as, pos, rtptime);
}
playback_write(as, buf, rtptime);
}
}
static int
alsa_flush(output_status_cb cb, uint64_t rtptime)
{
struct alsa_session *as;
int i;
i = 0;
for (as = sessions; as; as = as->next)
{
i++;
snd_pcm_drop(hdl);
prebuf_free(as);
as->status_cb = cb;
as->state = ALSA_STATE_STARTED;
alsa_status(as);
}
return i;
}
static void
alsa_set_status_cb(struct output_session *session, output_status_cb cb)
{
struct alsa_session *as = session->session;
as->status_cb = cb;
}
static int
alsa_init(void)
{
struct output_device *device;
cfg_t *cfg_audio;
char *nickname;
char *type;
cfg_audio = cfg_getsec(cfg, "audio");
type = cfg_getstr(cfg_audio, "type");
if (type && (strcasecmp(type, "alsa") != 0))
return -1;
card_name = cfg_getstr(cfg_audio, "card");
mixer_name = cfg_getstr(cfg_audio, "mixer");
nickname = cfg_getstr(cfg_audio, "nickname");
device = calloc(1, sizeof(struct output_device));
if (!device)
{
DPRINTF(E_LOG, L_LAUDIO, "Out of memory for ALSA device\n");
return -1;
}
device->id = 0;
device->name = nickname;
device->type = OUTPUT_TYPE_ALSA;
device->type_name = outputs_name(device->type);
device->advertised = 1;
device->has_video = 0;
DPRINTF(E_INFO, L_LAUDIO, "Adding ALSA device '%s' using friendly name '%s'\n", card_name, nickname);
player_device_add(device);
snd_lib_error_set_handler(logger_alsa);
hdl = NULL;
mixer_hdl = NULL;
vol_elem = NULL;
return 0;
}
static void
alsa_deinit(void)
{
snd_lib_error_set_handler(NULL);
}
struct output_definition output_alsa =
{
.name = "ALSA",
.type = OUTPUT_TYPE_ALSA,
.priority = 3,
.disabled = 0,
.init = alsa_init,
.deinit = alsa_deinit,
.device_start = alsa_device_start,
.device_stop = alsa_device_stop,
.device_probe = alsa_device_probe,
.device_volume_set = alsa_device_volume_set,
.playback_start = alsa_playback_start,
.playback_stop = alsa_playback_stop,
.write = alsa_write,
.flush = alsa_flush,
.status_cb = alsa_set_status_cb,
};

View File

@ -1770,6 +1770,8 @@ device_streaming_cb(struct output_device *device, struct output_session *session
{ {
int ret; int ret;
DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_streaming_cb\n", outputs_name(device->type));
ret = device_check(device); ret = device_check(device);
if (ret < 0) if (ret < 0)
{ {
@ -1814,6 +1816,8 @@ device_streaming_cb(struct output_device *device, struct output_session *session
static void static void
device_command_cb(struct output_device *device, struct output_session *session, enum output_device_state status) device_command_cb(struct output_device *device, struct output_session *session, enum output_device_state status)
{ {
DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_command_cb\n", outputs_name(device->type));
cur_cmd->output_requests_pending--; cur_cmd->output_requests_pending--;
outputs_status_cb(session, device_streaming_cb); outputs_status_cb(session, device_streaming_cb);
@ -1837,6 +1841,8 @@ device_shutdown_cb(struct output_device *device, struct output_session *session,
{ {
int ret; int ret;
DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_shutdown_cb\n", outputs_name(device->type));
cur_cmd->output_requests_pending--; cur_cmd->output_requests_pending--;
if (output_sessions) if (output_sessions)
@ -1871,6 +1877,8 @@ device_shutdown_cb(struct output_device *device, struct output_session *session,
static void static void
device_lost_cb(struct output_device *device, struct output_session *session, enum output_device_state status) device_lost_cb(struct output_device *device, struct output_session *session, enum output_device_state status)
{ {
DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_lost_cb\n", outputs_name(device->type));
/* We lost that device during startup for some reason, not much we can do here */ /* We lost that device during startup for some reason, not much we can do here */
if (status == OUTPUT_STATE_FAILED) if (status == OUTPUT_STATE_FAILED)
DPRINTF(E_WARN, L_PLAYER, "Failed to stop lost device\n"); DPRINTF(E_WARN, L_PLAYER, "Failed to stop lost device\n");
@ -1884,6 +1892,8 @@ device_activate_cb(struct output_device *device, struct output_session *session,
struct timespec ts; struct timespec ts;
int ret; int ret;
DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_activate_cb\n", outputs_name(device->type));
cur_cmd->output_requests_pending--; cur_cmd->output_requests_pending--;
ret = device_check(device); ret = device_check(device);
@ -1955,6 +1965,8 @@ device_probe_cb(struct output_device *device, struct output_session *session, en
{ {
int ret; int ret;
DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_probe_cb\n", outputs_name(device->type));
cur_cmd->output_requests_pending--; cur_cmd->output_requests_pending--;
ret = device_check(device); ret = device_check(device);
@ -2002,6 +2014,8 @@ device_restart_cb(struct output_device *device, struct output_session *session,
{ {
int ret; int ret;
DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_restart_cb\n", outputs_name(device->type));
cur_cmd->output_requests_pending--; cur_cmd->output_requests_pending--;
ret = device_check(device); ret = device_check(device);
@ -2233,7 +2247,7 @@ playback_stop(struct player_command *cmd)
metadata_purge(); metadata_purge();
/* We're async if we need to flush RAOP devices */ /* We're async if we need to flush devices */
if (cmd->output_requests_pending > 0) if (cmd->output_requests_pending > 0)
return 1; /* async */ return 1; /* async */
@ -2408,7 +2422,7 @@ playback_start_item(struct player_command *cmd, struct queue_item *qii)
return -1; return -1;
} }
/* We're async if we need to start RAOP devices */ /* We're async if we need to start devices */
if (cmd->output_requests_pending > 0) if (cmd->output_requests_pending > 0)
return 1; /* async */ return 1; /* async */
@ -2663,7 +2677,7 @@ playback_pause(struct player_command *cmd)
metadata_purge(); metadata_purge();
/* We're async if we need to flush RAOP devices */ /* We're async if we need to flush devices */
if (cmd->output_requests_pending > 0) if (cmd->output_requests_pending > 0)
return 1; /* async */ return 1; /* async */
@ -2809,8 +2823,8 @@ speaker_set(struct player_command *cmd)
if (cmd->ret != -2) if (cmd->ret != -2)
cmd->ret = -1; cmd->ret = -1;
} }
else
cmd->output_requests_pending += 1; cmd->output_requests_pending++;
} }
} }
else else
@ -2830,8 +2844,8 @@ speaker_set(struct player_command *cmd)
if (cmd->ret != -2) if (cmd->ret != -2)
cmd->ret = -1; cmd->ret = -1;
} }
else
cmd->output_requests_pending += 1; cmd->output_requests_pending++;
} }
} }
} }