mirror of
https://github.com/owntone/owntone-server.git
synced 2024-12-27 23:55:57 -05:00
[alsa] Add rewritten ALSA to generic outputs interface
This commit is contained in:
parent
f1fb86e7e1
commit
c5bb83480d
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
934
src/outputs/alsa.c
Normal 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,
|
||||||
|
};
|
28
src/player.c
28
src/player.c
@ -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++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user