[chromecast] Remove RAOP specifics from player.c, add generic output interface

This commit is contained in:
ejurgensen 2016-01-24 01:14:07 +01:00
parent 4c887ba7bb
commit ffe8653d9e
11 changed files with 1382 additions and 922 deletions

View File

@ -14,7 +14,7 @@ LASTFM_SRC=lastfm.c lastfm.h
endif
if COND_CHROMECAST
CHROMECAST_SRC=cast.h cast.c cast_channel.pb-c.h cast_channel.pb-c.c
CHROMECAST_SRC=outputs/cast.c outputs/cast_channel.pb-c.h outputs/cast_channel.pb-c.c
endif
if COND_MPD
@ -103,9 +103,10 @@ forked_daapd_SOURCES = main.c \
queue.c queue.h \
worker.c worker.h \
$(ALSA_SRC) $(OSS4_SRC) \
outputs.h outputs.c \
laudio_dummy.c \
laudio.c laudio.h \
raop.c raop.h \
outputs/raop.c \
evrtsp/rtsp.c evrtp/evrtsp.h evrtsp/rtsp-internal.h evrtsp/log.h \
$(SPOTIFY_SRC) \
$(LASTFM_SRC) \

View File

@ -1,58 +0,0 @@
#ifndef __CAST_H__
#define __CAST_H__
#include "raop.h"
int
cast_device_start(struct raop_device *rd, raop_status_cb cb);
/*void
raop_metadata_purge(void);
void
raop_metadata_prune(uint64_t rtptime);
struct raop_metadata *
raop_metadata_prepare(int id);
void
raop_metadata_send(struct raop_metadata *rmd, uint64_t rtptime, uint64_t offset, int startup);
int
raop_device_probe(struct raop_device *rd, raop_status_cb cb);
int
raop_device_start(struct raop_device *rd, raop_status_cb cb, uint64_t rtptime);
void
raop_device_stop(struct raop_session *rs);
void
raop_playback_start(uint64_t next_pkt, struct timespec *ts);
void
raop_playback_stop(void);
int
raop_set_volume_one(struct raop_session *rs, int volume, raop_status_cb cb);
int
raop_flush(raop_status_cb cb, uint64_t rtptime);
void
raop_set_status_cb(struct raop_session *rs, raop_status_cb cb);
void
raop_v2_write(uint8_t *buf, uint64_t rtptime);
*/
int
cast_init(void);
void
cast_deinit(void);
#endif /* !__CAST_H__ */

354
src/outputs.c Normal file
View File

@ -0,0 +1,354 @@
/*
* Copyright (C) 2016 Espen Jürgensen <espenjurgensen@gmail.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 "logger.h"
#include "outputs.h"
/*#ifdef ALSA
extern audio_output output_alsa;
#endif
#ifdef OSS4
extern audio_output output_oss4;
#endif
extern struct output_definition output_dummy;*/
extern struct output_definition output_raop;
#ifdef CHROMECAST
extern struct output_definition output_cast;
#endif
// Must be in sync with enum output_types
static struct output_definition *outputs[] = {
/*#ifdef ALSA
&output_alsa,
#endif
#ifdef OSS4
&output_oss4,
#endif
&output_dummy,*/
&output_raop,
#ifdef CHROMECAST
&output_cast,
#endif
NULL
};
int
outputs_device_start(struct output_device *device, output_status_cb cb, uint64_t rtptime)
{
if (outputs[device->type]->disabled)
return -1;
if (outputs[device->type]->device_start)
return outputs[device->type]->device_start(device, cb, rtptime);
else
return -1;
}
void
outputs_device_stop(struct output_session *session)
{
if (outputs[session->type]->disabled)
return;
if (outputs[session->type]->device_stop)
outputs[session->type]->device_stop(session);
}
int
outputs_device_probe(struct output_device *device, output_status_cb cb)
{
if (outputs[device->type]->disabled)
return -1;
if (outputs[device->type]->device_probe)
return outputs[device->type]->device_probe(device, cb);
else
return -1;
}
void
outputs_device_free(struct output_device *device)
{
if (outputs[device->type]->disabled)
DPRINTF(E_LOG, L_PLAYER, "BUG! Freeing device from a disabled output?\n");
if (outputs[device->type]->device_free_extra)
outputs[device->type]->device_free_extra(device);
if (device->name)
free(device->name);
if (device->v4_address)
free(device->v4_address);
if (device->v6_address)
free(device->v6_address);
if (device->session)
DPRINTF(E_LOG, L_PLAYER, "BUG! Freeing device with active session?\n");
free(device);
}
int
outputs_device_volume_set(struct output_device *device, output_status_cb cb)
{
if (outputs[device->type]->disabled)
return -1;
if (outputs[device->type]->device_volume_set)
return outputs[device->type]->device_volume_set(device, cb);
else
return -1;
}
void
outputs_playback_start(uint64_t next_pkt, struct timespec *ts)
{
int i;
for (i = 0; outputs[i]; i++)
{
if (outputs[i]->disabled)
continue;
if (outputs[i]->playback_start)
outputs[i]->playback_start(next_pkt, ts);
}
}
void
outputs_playback_stop(void)
{
int i;
for (i = 0; outputs[i]; i++)
{
if (outputs[i]->disabled)
continue;
if (outputs[i]->playback_stop)
outputs[i]->playback_stop();
}
}
void
outputs_write(uint8_t *buf, uint64_t rtptime)
{
int i;
for (i = 0; outputs[i]; i++)
{
if (outputs[i]->disabled)
continue;
if (outputs[i]->write)
outputs[i]->write(buf, rtptime);
}
}
// TODO output of this makes little sense
int
outputs_flush(output_status_cb cb, uint64_t rtptime)
{
int ret;
int i;
ret = 0;
for (i = 0; outputs[i]; i++)
{
if (outputs[i]->disabled)
continue;
if (outputs[i]->flush)
ret = outputs[i]->flush(cb, rtptime);
}
return ret;
}
void
outputs_status_cb(struct output_session *session, output_status_cb cb)
{
if (outputs[session->type]->disabled)
return;
if (outputs[session->type]->status_cb)
outputs[session->type]->status_cb(session, cb);
}
struct output_metadata *
outputs_metadata_prepare(int id)
{
struct output_metadata *omd;
struct output_metadata *new;
void *metadata;
int i;
omd = NULL;
for (i = 0; outputs[i]; i++)
{
if (outputs[i]->disabled)
continue;
if (!outputs[i]->metadata_prepare)
continue;
metadata = outputs[i]->metadata_prepare(id);
if (!metadata)
continue;
new = calloc(1, sizeof(struct output_metadata));
if (!new)
return omd;
if (omd)
new->next = omd;
omd = new;
omd->type = i;
omd->metadata = metadata;
}
return omd;
}
void
outputs_metadata_send(struct output_metadata *omd, uint64_t rtptime, uint64_t offset, int startup)
{
struct output_metadata *ptr;
int i;
for (i = 0; outputs[i]; i++)
{
if (outputs[i]->disabled)
continue;
if (!outputs[i]->metadata_send)
continue;
// Go through linked list to find appropriate metadata for type
for (ptr = omd; ptr; ptr = ptr->next)
if (ptr->type == i)
break;
if (!ptr)
continue;
outputs[i]->metadata_send(ptr->metadata, rtptime, offset, startup);
}
}
void
outputs_metadata_purge(void)
{
int i;
for (i = 0; outputs[i]; i++)
{
if (outputs[i]->disabled)
continue;
if (outputs[i]->metadata_purge)
outputs[i]->metadata_purge();
}
}
void
outputs_metadata_prune(uint64_t rtptime)
{
int i;
for (i = 0; outputs[i]; i++)
{
if (outputs[i]->disabled)
continue;
if (outputs[i]->metadata_prune)
outputs[i]->metadata_prune(rtptime);
}
}
void
outputs_metadata_free(struct output_metadata *omd)
{
struct output_metadata *ptr;
if (!omd)
return;
for (ptr = omd; omd; ptr = omd)
{
omd = ptr->next;
free(ptr);
}
}
const char *
outputs_name(enum output_types type)
{
return outputs[type]->name;
}
int
outputs_init(void)
{
int no_output;
int ret;
int i;
no_output = 1;
for (i = 0; outputs[i]; i++)
{
ret = outputs[i]->init();
if (ret < 0)
outputs[i]->disabled = 1;
else
no_output = 0;
}
if (no_output)
return -1;
return 0;
}
void
outputs_deinit(void)
{
int i;
for (i = 0; outputs[i]; i++)
{
if (!outputs[i]->disabled)
outputs[i]->deinit();
}
}

233
src/outputs.h Normal file
View File

@ -0,0 +1,233 @@
#ifndef __OUTPUTS_H__
#define __OUTPUTS_H__
/* Outputs is a generic interface between the player and a media output method,
* like for instance AirPlay (raop) or ALSA. The purpose of the interface is to
* make it easier to add new outputs without messing too much with the player or
* existing output methods.
*
* An output method will have a general type, and it will be able to detect
* supported devices that are available for output. A device will be typically
* be something like an AirPlay speaker.
*
* 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.
*
*/
enum output_types
{
/* OUTPUT_TYPE_ALSA,
OUTPUT_TYPE_OSS,
OUTPUT_TYPE_DUMMY,*/
OUTPUT_TYPE_RAOP,
#ifdef CHROMECAST
OUTPUT_TYPE_CAST,
#endif
};
/* Output session state */
/* Session is starting up */
#define OUTPUT_STATE_F_STARTUP (1 << 15)
/* Streaming is up (connection established) */
#define OUTPUT_STATE_F_CONNECTED (1 << 16)
enum output_device_state
{
OUTPUT_STATE_STOPPED = 0,
/* Session startup */
OUTPUT_STATE_OPTIONS = OUTPUT_STATE_F_STARTUP | 0x01,
OUTPUT_STATE_ANNOUNCE = OUTPUT_STATE_F_STARTUP | 0x02,
OUTPUT_STATE_SETUP = OUTPUT_STATE_F_STARTUP | 0x03,
OUTPUT_STATE_RECORD = OUTPUT_STATE_F_STARTUP | 0x04,
/* Session established
* - streaming ready (RECORD sent and acked, connection established)
* - commands (SET_PARAMETER) are possible
*/
OUTPUT_STATE_CONNECTED = OUTPUT_STATE_F_CONNECTED,
/* Audio data is being sent */
OUTPUT_STATE_STREAMING = OUTPUT_STATE_F_CONNECTED | 0x01,
/* Session is failed, couldn't startup or error occurred */
OUTPUT_STATE_FAILED = -1,
/* Password issue: unknown password or bad password */
OUTPUT_STATE_PASSWORD = -2,
};
/* Linked list of device info used by the player for each device
*/
struct output_device
{
// Device id
uint64_t id;
// Name of the device, e.g. "Living Room"
char *name;
// Type of the device, will be used to determine which output backend to call
enum output_types type;
// Type of output (string)
const char *type_name;
// Misc device flags
unsigned selected:1;
unsigned advertised:1;
unsigned has_password:1;
unsigned has_video:1;
// Password if relevant
const char *password;
// Device volume
int volume;
int relvol;
// Address
char *v4_address;
char *v6_address;
short v4_port;
short v6_port;
// Opaque pointers to device and session data
void *extra_device_info;
struct output_session *session;
struct output_device *next;
};
// Except for the type, sessions are opaque outside of the output backend
struct output_session
{
enum output_types type;
void *session;
};
// Linked list of metadata prepared by each output backend
struct output_metadata
{
enum output_types type;
void *metadata;
struct output_metadata *next;
};
typedef void (*output_status_cb)(struct output_device *device, struct output_session *session, enum output_device_state status);
struct output_definition
{
// Name of the output
const char *name;
// Type of output
enum output_types type;
// Priority to give this output when autoselecting an output, 1 is highest
// 1 = highest priority, 0 = don't autoselect
// TODO Not implemented yet
int priority;
// Set to 1 if the output initialization failed
int disabled;
// Initialization function called during startup
// Output must call device_cb when an output device becomes available/unavailable
int (*init)(void);
// Deinitialization function called at shutdown
void (*deinit)(void);
// Prepare a playback session on device and call back
int (*device_start)(struct output_device *device, output_status_cb cb, uint64_t rtptime);
// Close a session prepared by device_start
void (*device_stop)(struct output_session *session);
// Test the connection to a device and call back
int (*device_probe)(struct output_device *device, output_status_cb cb);
// Free the private device data
void (*device_free_extra)(struct output_device *device);
// Set the volume and call back
int (*device_volume_set)(struct output_device *device, output_status_cb cb);
// Start/stop playback on devices that were started
void (*playback_start)(uint64_t next_pkt, struct timespec *ts);
void (*playback_stop)(void);
// Write stream data to the output devices
void (*write)(uint8_t *buf, uint64_t rtptime);
// Flush all sessions
int (*flush)(output_status_cb cb, uint64_t rtptime);
// Change the call back associated with a session
void (*status_cb)(struct output_session *session, output_status_cb cb);
// Metadata
void *(*metadata_prepare)(int id);
void (*metadata_send)(void *metadata, uint64_t rtptime, uint64_t offset, int startup);
void (*metadata_purge)(void);
void (*metadata_prune)(uint64_t rtptime);
};
int
outputs_device_start(struct output_device *device, output_status_cb cb, uint64_t rtptime);
void
outputs_device_stop(struct output_session *session);
int
outputs_device_probe(struct output_device *device, output_status_cb cb);
void
outputs_device_free(struct output_device *device);
int
outputs_device_volume_set(struct output_device *device, output_status_cb cb);
void
outputs_playback_start(uint64_t next_pkt, struct timespec *ts);
void
outputs_playback_stop(void);
void
outputs_write(uint8_t *buf, uint64_t rtptime);
int
outputs_flush(output_status_cb cb, uint64_t rtptime);
void
outputs_status_cb(struct output_session *session, output_status_cb cb);
struct output_metadata *
outputs_metadata_prepare(int id);
void
outputs_metadata_send(struct output_metadata *omd, uint64_t rtptime, uint64_t offset, int startup);
void
outputs_metadata_purge(void);
void
outputs_metadata_prune(uint64_t rtptime);
void
outputs_metadata_free(struct output_metadata *omd);
const char *
outputs_name(enum output_types type);
int
outputs_init(void);
void
outputs_deinit(void);
#endif /* !__OUTPUTS_H__ */

View File

@ -392,6 +392,81 @@ cast_session_make(struct raop_device *rd, int family, raop_status_cb cb)
return NULL;
}
static void
cast_device_cb(const char *name, const char *type, const char *domain, const char *hostname, int family, const char *address, int port, struct keyval *txt)
{
struct raop_device *d;
const char *p;
uint32_t id;
p = keyval_get(txt, "id");
if (p)
id = djb_hash(p, strlen(p));
else
id = 0;
if (!id)
{
DPRINTF(E_LOG, L_PLAYER, "Could not extract ChromeCast device ID (%s)\n", name);
return;
}
DPRINTF(E_DBG, L_PLAYER, "Event for Chromecast device %s (port %d, id %" PRIu32 ")\n", name, port, id);
d = calloc(1, sizeof(struct raop_device));
if (!d)
{
DPRINTF(E_LOG, L_PLAYER, "Out of memory for new Chromecast device\n");
return;
}
d->id = id;
d->name = strdup(name);
if (port < 0)
{
/* Device stopped advertising */
switch (family)
{
case AF_INET:
d->v4_port = 1;
break;
case AF_INET6:
d->v6_port = 1;
break;
}
player_device_remove(d);
return;
}
/* Device type */
d->devtype = RAOP_DEV_CHROMECAST;
DPRINTF(E_INFO, L_PLAYER, "Adding Chromecast device %s\n", name);
d->advertised = 1;
switch (family)
{
case AF_INET:
d->v4_address = strdup(address);
d->v4_port = port;
break;
case AF_INET6:
d->v6_address = strdup(address);
d->v6_port = port;
break;
}
player_device_add(d);
}
int
cast_device_start(struct raop_device *rd, raop_status_cb cb)
{
@ -435,8 +510,19 @@ cast_device_start(struct raop_device *rd, raop_status_cb cb)
int
cast_init(void)
{
int mdns_flags;
int ret;
mdns_flags = MDNS_WANT_V4 | MDNS_WANT_V6 | MDNS_WANT_V6LL;
ret = mdns_browse("_googlecast._tcp", mdns_flags, cast_device_cb);
if (ret < 0)
{
DPRINTF(E_LOG, L_PLAYER, "Could not add mDNS browser for Chromecast devices\n");
goto mdns_browse_cast_fail;
}
// TODO Setting the cert file may not be required
if ( ((ret = gnutls_global_init()) != GNUTLS_E_SUCCESS) ||
((ret = gnutls_certificate_allocate_credentials(&tls_credentials)) != GNUTLS_E_SUCCESS) ||

View File

@ -56,6 +56,7 @@
#include <arpa/inet.h>
#include <net/if.h>
#include <netinet/in.h>
#include <event2/event.h>
#include <event2/buffer.h>
@ -64,12 +65,13 @@
#include "evrtsp/evrtsp.h"
#include "conffile.h"
#include "logger.h"
#include "mdns.h"
#include "misc.h"
#include "player.h"
#include "db.h"
#include "artwork.h"
#include "dmap_common.h"
#include "raop.h"
#include "outputs.h"
#ifndef MIN
# define MIN(a, b) ((a < b) ? a : b)
@ -88,6 +90,14 @@
/* This is an arbitrary value which just needs to be kept in sync with the config */
#define RAOP_CONFIG_MAX_VOLUME 11
union sockaddr_all
{
struct sockaddr_in sin;
struct sockaddr_in6 sin6;
struct sockaddr sa;
struct sockaddr_storage ss;
};
struct raop_v2_packet
{
uint8_t clear[AIRTUNES_V2_PKT_LEN];
@ -99,11 +109,28 @@ struct raop_v2_packet
struct raop_v2_packet *next;
};
enum raop_devtype {
RAOP_DEV_APEX1_80211G,
RAOP_DEV_APEX2_80211N,
RAOP_DEV_APEX3_80211N,
RAOP_DEV_APPLETV,
RAOP_DEV_OTHER,
};
// Info about the device, which is not required by the player, only internally
struct raop_extra
{
enum raop_devtype devtype;
unsigned encrypt:1;
unsigned wants_metadata:1;
};
struct raop_session
{
struct evrtsp_connection *ctrl;
enum raop_session_state state;
enum output_device_state state;
unsigned req_has_auth:1;
unsigned encrypt:1;
unsigned auth_quirk_itunes:1;
@ -127,8 +154,9 @@ struct raop_session
uint64_t start_rtptime;
/* Do not dereference - only passed to the status cb */
struct raop_device *dev;
raop_status_cb status_cb;
struct output_device *device;
struct output_session *output_session;
output_status_cb status_cb;
/* AirTunes v2 */
unsigned short server_port;
@ -202,6 +230,15 @@ static const uint8_t raop_rsa_pubkey[] =
static const uint8_t raop_rsa_exp[] = "\x01\x00\x01";
/* Keep in sync with enum raop_devtype */
static const char *raop_devtype[] =
{
"AirPort Express 1 - 802.11g",
"AirPort Express 2 - 802.11n",
"AirPort Express 3 - 802.11n",
"AppleTV",
"Other",
};
/* From player.c */
extern struct event_base *evbase_player;
@ -713,7 +750,7 @@ raop_metadata_free(struct raop_metadata *rmd)
free(rmd);
}
void
static void
raop_metadata_purge(void)
{
struct raop_metadata *rmd;
@ -728,7 +765,7 @@ raop_metadata_purge(void)
metadata_tail = NULL;
}
void
static void
raop_metadata_prune(uint64_t rtptime)
{
struct raop_metadata *rmd;
@ -748,7 +785,7 @@ raop_metadata_prune(uint64_t rtptime)
}
/* Thread: worker */
struct raop_metadata *
static void *
raop_metadata_prepare(int id)
{
struct query_params qp;
@ -1135,7 +1172,7 @@ raop_add_headers(struct raop_session *rs, struct evrtsp_request *req, enum evrts
DPRINTF(E_LOG, L_RAOP, "Could not add Authorization header\n");
if (ret == -2)
rs->state = RAOP_PASSWORD;
rs->state = OUTPUT_STATE_PASSWORD;
return -1;
}
@ -1652,6 +1689,8 @@ raop_session_free(struct raop_session *rs)
if (rs->devname)
free(rs->devname);
free(rs->output_session);
free(rs);
}
@ -1698,9 +1737,9 @@ static void
raop_session_failure(struct raop_session *rs)
{
/* Session failed, let our user know */
if (rs->state != RAOP_PASSWORD)
rs->state = RAOP_FAILED;
rs->status_cb(rs->dev, rs, rs->state);
if (rs->state != OUTPUT_STATE_PASSWORD)
rs->state = OUTPUT_STATE_FAILED;
rs->status_cb(rs->device, rs->output_session, rs->state);
raop_session_cleanup(rs);
}
@ -1727,21 +1766,25 @@ raop_rtsp_close_cb(struct evrtsp_connection *evcon, void *arg)
DPRINTF(E_LOG, L_RAOP, "ApEx %s closed RTSP connection\n", rs->devname);
rs->state = RAOP_FAILED;
rs->state = OUTPUT_STATE_FAILED;
evutil_timerclear(&tv);
evtimer_add(rs->deferredev, &tv);
}
static struct raop_session *
raop_session_make(struct raop_device *rd, int family, raop_status_cb cb)
raop_session_make(struct output_device *rd, int family, output_status_cb cb)
{
struct output_session *os;
struct raop_session *rs;
struct raop_extra *re;
char *address;
char *intf;
unsigned short port;
int ret;
re = rd->extra_device_info;
switch (family)
{
case AF_INET:
@ -1765,27 +1808,30 @@ raop_session_make(struct raop_device *rd, int family, raop_status_cb cb)
return NULL;
}
rs = (struct raop_session *)malloc(sizeof(struct raop_session));
if (!rs)
os = calloc(1, sizeof(struct output_session));
rs = calloc(1, sizeof(struct raop_session));
if (!os || !rs)
{
DPRINTF(E_LOG, L_RAOP, "Out of memory for RAOP session\n");
return NULL;
}
memset(rs, 0, sizeof(struct raop_session));
os->session = rs;
os->type = rd->type;
rs->state = RAOP_STOPPED;
rs->output_session = os;
rs->state = OUTPUT_STATE_STOPPED;
rs->reqs_in_flight = 0;
rs->cseq = 1;
rs->dev = rd;
rs->device = rd;
rs->status_cb = cb;
rs->server_fd = -1;
rs->password = rd->password;
rs->wants_metadata = rd->wants_metadata;
rs->wants_metadata = re->wants_metadata;
switch (rd->devtype)
switch (re->devtype)
{
case RAOP_DEV_APEX1_80211G:
rs->encrypt = 1;
@ -1808,7 +1854,7 @@ raop_session_make(struct raop_device *rd, int family, raop_status_cb cb)
break;
case RAOP_DEV_OTHER:
rs->encrypt = rd->encrypt;
rs->encrypt = re->encrypt;
rs->auth_quirk_itunes = 0;
break;
}
@ -2156,13 +2202,15 @@ raop_metadata_startup_send(struct raop_session *rs)
}
}
void
raop_metadata_send(struct raop_metadata *rmd, uint64_t rtptime, uint64_t offset, int startup)
static void
raop_metadata_send(void *metadata, uint64_t rtptime, uint64_t offset, int startup)
{
struct raop_metadata *rmd;
struct raop_session *rs;
uint32_t delay;
int ret;
rmd = metadata;
rmd->start += rtptime;
rmd->end += rtptime;
@ -2177,7 +2225,7 @@ raop_metadata_send(struct raop_metadata *rmd, uint64_t rtptime, uint64_t offset,
for (rs = sessions; rs; rs = rs->next)
{
if (!(rs->state & RAOP_F_CONNECTED))
if (!(rs->state & OUTPUT_STATE_F_CONNECTED))
continue;
if (!rs->wants_metadata)
@ -2269,7 +2317,7 @@ raop_set_volume_internal(struct raop_session *rs, int volume, evrtsp_req_cb cb)
static void
raop_cb_set_volume(struct evrtsp_request *req, void *arg)
{
raop_status_cb status_cb;
output_status_cb status_cb;
struct raop_session *rs;
int ret;
@ -2294,7 +2342,7 @@ raop_cb_set_volume(struct evrtsp_request *req, void *arg)
/* Let our user know */
status_cb = rs->status_cb;
rs->status_cb = NULL;
status_cb(rs->dev, rs, rs->state);
status_cb(rs->device, rs->output_session, rs->state);
if (!rs->reqs_in_flight)
evrtsp_connection_set_closecb(rs->ctrl, raop_rtsp_close_cb, rs);
@ -2306,15 +2354,21 @@ raop_cb_set_volume(struct evrtsp_request *req, void *arg)
}
/* Volume in [0 - 100] */
int
raop_set_volume_one(struct raop_session *rs, int volume, raop_status_cb cb)
static int
raop_set_volume_one(struct output_device *rd, output_status_cb cb)
{
struct raop_session *rs;
int ret;
if (!(rs->state & RAOP_F_CONNECTED))
if (!rd->session || !rd->session->session)
return 0;
ret = raop_set_volume_internal(rs, volume, raop_cb_set_volume);
rs = rd->session->session;
if (!(rs->state & OUTPUT_STATE_F_CONNECTED))
return 0;
ret = raop_set_volume_internal(rs, rd->volume, raop_cb_set_volume);
if (ret < 0)
{
raop_session_failure(rs);
@ -2330,7 +2384,7 @@ raop_set_volume_one(struct raop_session *rs, int volume, raop_status_cb cb)
static void
raop_cb_flush(struct evrtsp_request *req, void *arg)
{
raop_status_cb status_cb;
output_status_cb status_cb;
struct raop_session *rs;
int ret;
@ -2352,12 +2406,12 @@ raop_cb_flush(struct evrtsp_request *req, void *arg)
if (ret < 0)
goto error;
rs->state = RAOP_CONNECTED;
rs->state = OUTPUT_STATE_CONNECTED;
/* Let our user know */
status_cb = rs->status_cb;
rs->status_cb = NULL;
status_cb(rs->dev, rs, rs->state);
status_cb(rs->device, rs->output_session, rs->state);
if (!rs->reqs_in_flight)
evrtsp_connection_set_closecb(rs->ctrl, raop_rtsp_close_cb, rs);
@ -2368,6 +2422,10 @@ raop_cb_flush(struct evrtsp_request *req, void *arg)
raop_session_failure(rs);
}
// Forward
static void
raop_device_stop(struct output_session *session);
static void
raop_flush_timer_cb(int fd, short what, void *arg)
{
@ -2377,15 +2435,15 @@ raop_flush_timer_cb(int fd, short what, void *arg)
for (rs = sessions; rs; rs = rs->next)
{
if (!(rs->state & RAOP_F_CONNECTED))
if (!(rs->state & OUTPUT_STATE_F_CONNECTED))
continue;
raop_device_stop(rs);
raop_device_stop(rs->output_session);
}
}
int
raop_flush(raop_status_cb cb, uint64_t rtptime)
static int
raop_flush(output_status_cb cb, uint64_t rtptime)
{
struct timeval tv;
struct raop_session *rs;
@ -2395,7 +2453,7 @@ raop_flush(raop_status_cb cb, uint64_t rtptime)
pending = 0;
for (rs = sessions; rs; rs = rs->next)
{
if (rs->state != RAOP_STREAMING)
if (rs->state != OUTPUT_STATE_STREAMING)
continue;
ret = raop_send_req_flush(rs, rtptime, raop_cb_flush);
@ -2767,7 +2825,7 @@ raop_v2_control_send_sync(uint64_t next_pkt, struct timespec *init)
for (rs = sessions; rs; rs = rs->next)
{
if (rs->state != RAOP_STREAMING)
if (rs->state != OUTPUT_STATE_STREAMING)
continue;
switch (rs->sa.ss.ss_family)
@ -3198,7 +3256,11 @@ raop_v2_send_packet(struct raop_session *rs, struct raop_v2_packet *pkt)
return 0;
}
void
// Forward
static void
raop_playback_stop(void);
static void
raop_v2_write(uint8_t *buf, uint64_t rtptime)
{
struct raop_v2_packet *pkt;
@ -3223,7 +3285,7 @@ raop_v2_write(uint8_t *buf, uint64_t rtptime)
for (rs = sessions; rs; rs = rs->next)
{
if (rs->state != RAOP_STREAMING)
if (rs->state != OUTPUT_STATE_STREAMING)
continue;
raop_v2_send_packet(rs, pkt);
@ -3341,9 +3403,9 @@ raop_v2_stream_open(struct raop_session *rs)
* playback is in progress.
*/
if (sync_counter != 0)
rs->state = RAOP_STREAMING;
rs->state = OUTPUT_STATE_STREAMING;
else
rs->state = RAOP_CONNECTED;
rs->state = OUTPUT_STATE_CONNECTED;
return 0;
@ -3369,7 +3431,7 @@ raop_startup_cancel(struct raop_session *rs)
static void
raop_cb_startup_volume(struct evrtsp_request *req, void *arg)
{
raop_status_cb status_cb;
output_status_cb status_cb;
struct raop_session *rs;
int ret;
@ -3405,7 +3467,7 @@ raop_cb_startup_volume(struct evrtsp_request *req, void *arg)
status_cb = rs->status_cb;
rs->status_cb = NULL;
status_cb(rs->dev, rs, rs->state);
status_cb(rs->device, rs->output_session, rs->state);
if (!rs->reqs_in_flight)
evrtsp_connection_set_closecb(rs->ctrl, raop_rtsp_close_cb, rs);
@ -3448,7 +3510,7 @@ raop_cb_startup_record(struct evrtsp_request *req, void *arg)
else
DPRINTF(E_DBG, L_RAOP, "RAOP audio latency is %s\n", param);
rs->state = RAOP_RECORD;
rs->state = OUTPUT_STATE_RECORD;
/* Set initial volume */
raop_set_volume_internal(rs, rs->volume, raop_cb_startup_volume);
@ -3596,7 +3658,7 @@ raop_cb_startup_setup(struct evrtsp_request *req, void *arg)
DPRINTF(E_DBG, L_RAOP, "Negotiated AirTunes v2 UDP streaming session %s; ports s=%u c=%u t=%u\n", rs->session, rs->server_port, rs->control_port, rs->timing_port);
rs->state = RAOP_SETUP;
rs->state = OUTPUT_STATE_SETUP;
/* Send RECORD */
ret = raop_send_req_record(rs, raop_cb_startup_record);
@ -3633,7 +3695,7 @@ raop_cb_startup_announce(struct evrtsp_request *req, void *arg)
if (ret < 0)
goto cleanup;
rs->state = RAOP_ANNOUNCE;
rs->state = OUTPUT_STATE_ANNOUNCE;
/* Send SETUP */
ret = raop_send_req_setup(rs, raop_cb_startup_setup);
@ -3676,7 +3738,7 @@ raop_cb_startup_options(struct evrtsp_request *req, void *arg)
{
DPRINTF(E_LOG, L_RAOP, "Bad password for device %s\n", rs->devname);
rs->state = RAOP_PASSWORD;
rs->state = OUTPUT_STATE_PASSWORD;
goto cleanup;
}
@ -3695,7 +3757,7 @@ raop_cb_startup_options(struct evrtsp_request *req, void *arg)
return;
}
rs->state = RAOP_OPTIONS;
rs->state = OUTPUT_STATE_OPTIONS;
/* Send ANNOUNCE */
ret = raop_send_req_announce(rs, raop_cb_startup_announce);
@ -3712,7 +3774,7 @@ raop_cb_startup_options(struct evrtsp_request *req, void *arg)
static void
raop_cb_shutdown_teardown(struct evrtsp_request *req, void *arg)
{
raop_status_cb status_cb;
output_status_cb status_cb;
struct raop_session *rs;
int ret;
@ -3734,13 +3796,13 @@ raop_cb_shutdown_teardown(struct evrtsp_request *req, void *arg)
if (ret < 0)
goto error;
rs->state = RAOP_STOPPED;
rs->state = OUTPUT_STATE_STOPPED;
/* Session shut down, tell our user */
status_cb = rs->status_cb;
rs->status_cb = NULL;
status_cb(rs->dev, rs, rs->state);
status_cb(rs->device, rs->output_session, rs->state);
raop_session_cleanup(rs);
@ -3753,7 +3815,7 @@ raop_cb_shutdown_teardown(struct evrtsp_request *req, void *arg)
static void
raop_cb_probe_options(struct evrtsp_request *req, void *arg)
{
raop_status_cb status_cb;
output_status_cb status_cb;
struct raop_session *rs;
int ret;
@ -3781,7 +3843,7 @@ raop_cb_probe_options(struct evrtsp_request *req, void *arg)
{
DPRINTF(E_LOG, L_RAOP, "Bad password for device %s\n", rs->devname);
rs->state = RAOP_PASSWORD;
rs->state = OUTPUT_STATE_PASSWORD;
goto cleanup;
}
@ -3800,13 +3862,13 @@ raop_cb_probe_options(struct evrtsp_request *req, void *arg)
return;
}
rs->state = RAOP_OPTIONS;
rs->state = OUTPUT_STATE_OPTIONS;
/* Device probed successfully, tell our user */
status_cb = rs->status_cb;
rs->status_cb = NULL;
status_cb(rs->dev, rs, rs->state);
status_cb(rs->device, rs->output_session, rs->state);
/* We're not going further with this session */
raop_session_cleanup(rs);
@ -3818,8 +3880,213 @@ raop_cb_probe_options(struct evrtsp_request *req, void *arg)
}
int
raop_device_probe(struct raop_device *rd, raop_status_cb cb)
/* RAOP devices discovery - mDNS callback */
/* Thread: main (mdns) */
/* Examples of txt content:
* Apple TV 2:
["sf=0x4" "am=AppleTV2,1" "vs=130.14" "vn=65537" "tp=UDP" "ss=16" "sr=4 4100" "sv=false" "pw=false" "md=0,1,2" "et=0,3,5" "da=true" "cn=0,1,2,3" "ch=2"]
["sf=0x4" "am=AppleTV2,1" "vs=105.5" "md=0,1,2" "tp=TCP,UDP" "vn=65537" "pw=false" "ss=16" "sr=44100" "da=true" "sv=false" "et=0,3" "cn=0,1" "ch=2" "txtvers=1"]
* Apple TV 3:
["vv=2" "vs=200.54" "vn=65537" "tp=UDP" "sf=0x44" "pk=8...f" "am=AppleTV3,1" "md=0,1,2" "ft=0x5A7FFFF7,0xE" "et=0,3,5" "da=true" "cn=0,1,2,3"]
* Sony STR-DN1040:
["fv=s9327.1090.0" "am=STR-DN1040" "vs=141.9" "vn=65537" "tp=UDP" "ss=16" "sr=44100" "sv=false" "pw=false" "md=0,2" "ft=0x44F0A00" "et=0,4" "da=true" "cn=0,1" "ch=2" "txtvers=1"]
* AirFoil:
["rastx=iafs" "sm=false" "raver=3.5.3.0" "ek=1" "md=0,1,2" "ramach=Win32NT.6" "et=0,1" "cn=0,1" "sr=44100" "ss=16" "raAudioFormats=ALAC" "raflakyzeroconf=true" "pw=false" "rast=afs" "vn=3" "sv=false" "txtvers=1" "ch=2" "tp=UDP"]
* Xbmc 13:
["am=Xbmc,1" "md=0,1,2" "vs=130.14" "da=true" "vn=3" "pw=false" "sr=44100" "ss=16" "sm=false" "tp=UDP" "sv=false" "et=0,1" "ek=1" "ch=2" "cn=0,1" "txtvers=1"]
* Shairport (abrasive/1.0):
["pw=false" "txtvers=1" "vn=3" "sr=44100" "ss=16" "ch=2" "cn=0,1" "et=0,1" "ek=1" "sm=false" "tp=UDP"]
* JB2:
["fv=95.8947" "am=JB2 Gen" "vs=103.2" "tp=UDP" "vn=65537" "pw=false" "s s=16" "sr=44100" "da=true" "sv=false" "et=0,4" "cn=0,1" "ch=2" "txtvers=1"]
* Airport Express 802.11g (Gen 1):
["tp=TCP,UDP" "sm=false" "sv=false" "ek=1" "et=0,1" "cn=0,1" "ch=2" "ss=16" "sr=44100" "pw=false" "vn=3" "txtvers=1"]
* Airport Express 802.11n:
802.11n Gen 2 model (firmware 7.6.4): "am=Airport4,107", "et=0,1"
802.11n Gen 3 model (firmware 7.6.4): "am=Airport10,115", "et=0,4"
*/
static void
raop_device_cb(const char *name, const char *type, const char *domain, const char *hostname, int family, const char *address, int port, struct keyval *txt)
{
struct output_device *rd;
struct raop_extra *re;
cfg_t *airplay;
const char *p;
char *at_name;
char *password;
uint64_t id;
int ret;
ret = safe_hextou64(name, &id);
if (ret < 0)
{
DPRINTF(E_LOG, L_RAOP, "Could not extract AirPlay device ID (%s)\n", name);
return;
}
at_name = strchr(name, '@');
if (!at_name)
{
DPRINTF(E_LOG, L_RAOP, "Could not extract AirPlay device name (%s)\n", name);
return;
}
at_name++;
DPRINTF(E_DBG, L_RAOP, "Event for AirPlay device %s (port %d, id %" PRIx64 ")\n", at_name, port, id);
rd = calloc(1, sizeof(struct output_device));
re = calloc(1, sizeof(struct raop_extra));
if (!rd || !re)
{
DPRINTF(E_LOG, L_RAOP, "Out of memory for new AirPlay device\n");
return;
}
rd->id = id;
rd->name = strdup(at_name);
rd->type = OUTPUT_TYPE_RAOP;
rd->type_name = outputs_name(rd->type);
rd->extra_device_info = re;
if (port < 0)
{
/* Device stopped advertising */
switch (family)
{
case AF_INET:
rd->v4_port = 1;
break;
case AF_INET6:
rd->v6_port = 1;
break;
}
ret = player_device_remove(rd);
if (ret < 0)
goto free_rd;
return;
}
/* Protocol */
p = keyval_get(txt, "tp");
if (!p)
{
DPRINTF(E_LOG, L_RAOP, "AirPlay %s: no tp field in TXT record!\n", name);
goto free_rd;
}
if (*p == '\0')
{
DPRINTF(E_LOG, L_RAOP, "AirPlay %s: tp has no value\n", name);
goto free_rd;
}
if (!strstr(p, "UDP"))
{
DPRINTF(E_LOG, L_RAOP, "AirPlay %s: device does not support AirTunes v2 (tp=%s), discarding\n", name, p);
goto free_rd;
}
/* Password protection */
password = NULL;
p = keyval_get(txt, "pw");
if (!p)
{
DPRINTF(E_INFO, L_RAOP, "AirPlay %s: no pw field in TXT record, assuming no password protection\n", name);
rd->has_password = 0;
}
else if (*p == '\0')
{
DPRINTF(E_LOG, L_RAOP, "AirPlay %s: pw has no value\n", name);
goto free_rd;
}
else
{
rd->has_password = (strcmp(p, "false") != 0);
}
if (rd->has_password)
{
DPRINTF(E_LOG, L_RAOP, "AirPlay device %s is password-protected\n", name);
airplay = cfg_gettsec(cfg, "airplay", at_name);
if (airplay)
password = cfg_getstr(airplay, "password");
if (!password)
DPRINTF(E_LOG, L_RAOP, "No password given in config for AirPlay device %s\n", name);
}
rd->password = password;
/* Device type */
re->devtype = RAOP_DEV_OTHER;
p = keyval_get(txt, "am");
if (!p)
re->devtype = RAOP_DEV_APEX1_80211G; // First generation AirPort Express
else if (strncmp(p, "AirPort4", strlen("AirPort4")) == 0)
re->devtype = RAOP_DEV_APEX2_80211N; // Second generation
else if (strncmp(p, "AirPort", strlen("AirPort")) == 0)
re->devtype = RAOP_DEV_APEX3_80211N; // Third generation and newer
else if (strncmp(p, "AppleTV", strlen("AppleTV")) == 0)
re->devtype = RAOP_DEV_APPLETV;
else if (*p == '\0')
DPRINTF(E_LOG, L_RAOP, "AirPlay %s: am has no value\n", name);
/* Encrypt stream */
p = keyval_get(txt, "ek");
if (p && (*p == '1'))
re->encrypt = 1;
else
re->encrypt = 0;
/* Metadata support */
p = keyval_get(txt, "md");
if (p && (*p != '\0'))
re->wants_metadata = 1;
else
re->wants_metadata = 0;
DPRINTF(E_INFO, L_RAOP, "Adding AirPlay device %s: password: %u, encrypt: %u, metadata: %u, type %s\n",
name, rd->has_password, re->encrypt, re->wants_metadata, raop_devtype[re->devtype]);
rd->advertised = 1;
switch (family)
{
case AF_INET:
rd->v4_address = strdup(address);
rd->v4_port = port;
break;
case AF_INET6:
rd->v6_address = strdup(address);
rd->v6_port = port;
break;
}
ret = player_device_add(rd);
if (ret < 0)
goto free_rd;
return;
free_rd:
outputs_device_free(rd);
}
static int
raop_device_probe(struct output_device *rd, output_status_cb cb)
{
struct raop_session *rs;
int ret;
@ -3858,8 +4125,8 @@ raop_device_probe(struct raop_device *rd, raop_status_cb cb)
return 0;
}
int
raop_device_start(struct raop_device *rd, raop_status_cb cb, uint64_t rtptime)
static int
raop_device_start(struct output_device *rd, output_status_cb cb, uint64_t rtptime)
{
struct raop_session *rs;
int ret;
@ -3903,17 +4170,25 @@ raop_device_start(struct raop_device *rd, raop_status_cb cb, uint64_t rtptime)
return 0;
}
void
raop_device_stop(struct raop_session *rs)
static void
raop_device_stop(struct output_session *session)
{
if (!(rs->state & RAOP_F_CONNECTED))
struct raop_session *rs = session->session;
if (!(rs->state & OUTPUT_STATE_F_CONNECTED))
raop_session_cleanup(rs);
else
raop_send_req_teardown(rs, raop_cb_shutdown_teardown);
}
void
static void
raop_device_free_extra(struct output_device *device)
{
free(device->extra_device_info);
}
static void
raop_playback_start(uint64_t next_pkt, struct timespec *ts)
{
struct raop_session *rs;
@ -3924,15 +4199,15 @@ raop_playback_start(uint64_t next_pkt, struct timespec *ts)
for (rs = sessions; rs; rs = rs->next)
{
if (rs->state == RAOP_CONNECTED)
rs->state = RAOP_STREAMING;
if (rs->state == OUTPUT_STATE_CONNECTED)
rs->state = OUTPUT_STATE_STREAMING;
}
/* Send initial playback sync */
raop_v2_control_send_sync(next_pkt, ts);
}
void
static void
raop_playback_stop(void)
{
struct raop_session *rs;
@ -3946,20 +4221,23 @@ raop_playback_stop(void)
}
}
void
raop_set_status_cb(struct raop_session *rs, raop_status_cb cb)
static void
raop_set_status_cb(struct output_session *session, output_status_cb cb)
{
struct raop_session *rs = session->session;
rs->status_cb = cb;
}
int
raop_init(int *v6enabled)
static int
raop_init(void)
{
char ebuf[64];
char *ptr;
char *libname;
gpg_error_t gc_err;
int v6enabled;
int mdns_flags;
int ret;
timing_4svc.fd = -1;
@ -4048,7 +4326,9 @@ raop_init(int *v6enabled)
goto out_free_b64_iv;
}
ret = raop_v2_timing_start(*v6enabled);
v6enabled = cfg_getbool(cfg_getsec(cfg, "general"), "ipv6");
ret = raop_v2_timing_start(v6enabled);
if (ret < 0)
{
DPRINTF(E_LOG, L_RAOP, "AirTunes v2 time synchronization failed to start\n");
@ -4056,7 +4336,7 @@ raop_init(int *v6enabled)
goto out_free_flush_timer;
}
ret = raop_v2_control_start(*v6enabled);
ret = raop_v2_control_start(v6enabled);
if (ret < 0)
{
DPRINTF(E_LOG, L_RAOP, "AirTunes v2 playback synchronization failed to start\n");
@ -4064,11 +4344,27 @@ raop_init(int *v6enabled)
goto out_stop_timing;
}
if (*v6enabled)
*v6enabled = !((timing_6svc.fd < 0) || (control_6svc.fd < 0));
if (v6enabled)
v6enabled = !((timing_6svc.fd < 0) || (control_6svc.fd < 0));
if (v6enabled)
mdns_flags = MDNS_WANT_V4 | MDNS_WANT_V6 | MDNS_WANT_V6LL;
else
mdns_flags = MDNS_WANT_V4;
ret = mdns_browse("_raop._tcp", mdns_flags, raop_device_cb);
if (ret < 0)
{
DPRINTF(E_LOG, L_RAOP, "Could not add mDNS browser for AirPlay devices\n");
goto out_stop_control;
}
return 0;
out_stop_control:
raop_v2_control_stop();
out_stop_timing:
raop_v2_timing_stop();
out_free_flush_timer:
@ -4083,7 +4379,7 @@ raop_init(int *v6enabled)
return -1;
}
void
static void
raop_deinit(void)
{
struct raop_session *rs;
@ -4105,3 +4401,27 @@ raop_deinit(void)
free(raop_aes_key_b64);
free(raop_aes_iv_b64);
}
struct output_definition output_raop =
{
.name = "AirPlay",
.type = OUTPUT_TYPE_RAOP,
.priority = 1,
.disabled = 0,
.init = raop_init,
.deinit = raop_deinit,
.device_start = raop_device_start,
.device_stop = raop_device_stop,
.device_probe = raop_device_probe,
.device_free_extra = raop_device_free_extra,
.device_volume_set = raop_set_volume_one,
.playback_start = raop_playback_start,
.playback_stop = raop_playback_stop,
.write = raop_v2_write,
.flush = raop_flush,
.status_cb = raop_set_status_cb,
.metadata_prepare = raop_metadata_prepare,
.metadata_send = raop_metadata_send,
.metadata_purge = raop_metadata_purge,
.metadata_prune = raop_metadata_prune,
};

File diff suppressed because it is too large Load Diff

View File

@ -190,6 +190,11 @@ player_queue_clear_history(void);
void
player_queue_plid(uint32_t plid);
int
player_device_add(void *device);
int
player_device_remove(void *device);
struct player_history *
player_history_get(void);

View File

@ -1,139 +0,0 @@
#ifndef __RAOP_H__
#define __RAOP_H__
#include <netinet/in.h>
union sockaddr_all
{
struct sockaddr_in sin;
struct sockaddr_in6 sin6;
struct sockaddr sa;
struct sockaddr_storage ss;
};
/* Keep in sync with raop_devtype[] in player.c */
enum raop_devtype {
RAOP_DEV_APEX1_80211G,
RAOP_DEV_APEX2_80211N,
RAOP_DEV_APEX3_80211N,
RAOP_DEV_APPLETV,
RAOP_DEV_CHROMECAST,
RAOP_DEV_OTHER,
};
struct raop_session;
struct raop_metadata;
struct raop_device
{
uint64_t id;
char *name;
char *v4_address;
char *v6_address;
short v4_port;
short v6_port;
enum raop_devtype devtype;
unsigned selected:1;
unsigned advertised:1;
unsigned encrypt:1;
unsigned wants_metadata:1;
unsigned has_password:1;
const char *password;
int volume;
int relvol;
struct raop_session *session;
struct raop_device *next;
};
/* RAOP session state */
/* Session is starting up */
#define RAOP_F_STARTUP (1 << 15)
/* Streaming is up (connection established) */
#define RAOP_F_CONNECTED (1 << 16)
enum raop_session_state
{
RAOP_STOPPED = 0,
/* Session startup */
RAOP_OPTIONS = RAOP_F_STARTUP | 0x01,
RAOP_ANNOUNCE = RAOP_F_STARTUP | 0x02,
RAOP_SETUP = RAOP_F_STARTUP | 0x03,
RAOP_RECORD = RAOP_F_STARTUP | 0x04,
/* Session established
* - streaming ready (RECORD sent and acked, connection established)
* - commands (SET_PARAMETER) are possible
*/
RAOP_CONNECTED = RAOP_F_CONNECTED,
/* Audio data is being sent */
RAOP_STREAMING = RAOP_F_CONNECTED | 0x01,
/* Session is failed, couldn't startup or error occurred */
RAOP_FAILED = -1,
/* Password issue: unknown password or bad password */
RAOP_PASSWORD = -2,
};
typedef void (*raop_status_cb)(struct raop_device *dev, struct raop_session *rs, enum raop_session_state status);
void
raop_metadata_purge(void);
void
raop_metadata_prune(uint64_t rtptime);
struct raop_metadata *
raop_metadata_prepare(int id);
void
raop_metadata_send(struct raop_metadata *rmd, uint64_t rtptime, uint64_t offset, int startup);
int
raop_device_probe(struct raop_device *rd, raop_status_cb cb);
int
raop_device_start(struct raop_device *rd, raop_status_cb cb, uint64_t rtptime);
void
raop_device_stop(struct raop_session *rs);
void
raop_playback_start(uint64_t next_pkt, struct timespec *ts);
void
raop_playback_stop(void);
int
raop_set_volume_one(struct raop_session *rs, int volume, raop_status_cb cb);
int
raop_flush(raop_status_cb cb, uint64_t rtptime);
void
raop_set_status_cb(struct raop_session *rs, raop_status_cb cb);
void
raop_v2_write(uint8_t *buf, uint64_t rtptime);
int
raop_init(int *v6enabled);
void
raop_deinit(void);
#endif /* !__RAOP_H__ */