From 62b42ce354d687f23dde6901327556c26637a87d Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Sun, 7 Jan 2024 23:12:03 +0100 Subject: [PATCH] [misc/player] Introduce output ability to announce supported formats Also introduce default output format and selected device format, should the user want another format. As part of this, change enum player_format in player.h to enum media_format in misc.h so that it is akin to struct media_quality. Modify json API to support this. --- docs/json-api.md | 11 ++++++--- src/cache.c | 40 +++++++++++++++++++++++++++------ src/cache.h | 4 ++++ src/db.c | 4 ++-- src/httpd.c | 50 +++++++++++++++++++++++++++++++++-------- src/httpd.h | 11 --------- src/httpd_jsonapi.c | 50 +++++++++++------------------------------ src/httpd_streaming.c | 4 ++-- src/misc.c | 34 ++++++++++++++++++++++++++++ src/misc.h | 21 +++++++++++++++++ src/outputs.h | 6 ++++- src/outputs/airplay.c | 1 + src/outputs/alsa.c | 1 + src/outputs/cast.c | 1 + src/outputs/fifo.c | 1 + src/outputs/pulse.c | 1 + src/outputs/raop.c | 1 + src/outputs/rcp.c | 2 ++ src/outputs/streaming.c | 16 ++++++------- src/player.c | 38 +++++++++++++++++++++---------- src/player.h | 16 ++++--------- 21 files changed, 209 insertions(+), 104 deletions(-) diff --git a/docs/json-api.md b/docs/json-api.md index a988df25..8de4674d 100644 --- a/docs/json-api.md +++ b/docs/json-api.md @@ -342,6 +342,7 @@ GET /api/outputs | needs_auth_key | boolean | `true` if output requires an authorization key (device verification) | | volume | integer | Volume in percent (0 - 100) | | format | string | Stream format | +| supported_formats | array | Array of formats supported by output | **Example** @@ -361,7 +362,8 @@ curl -X GET "http://localhost:3689/api/outputs" "requires_auth": false, "needs_auth_key": false, "volume": 0, - "format": "alac" + "format": "alac", + "supported_formats": [ "alac" ] }, { "id": "0", @@ -372,7 +374,8 @@ curl -X GET "http://localhost:3689/api/outputs" "requires_auth": false, "needs_auth_key": false, "volume": 19, - "format": "pcm" + "format": "pcm", + "supported_formats": [ "pcm" ] }, { "id": "100", @@ -383,7 +386,8 @@ curl -X GET "http://localhost:3689/api/outputs" "requires_auth": false, "needs_auth_key": false, "volume": 0, - "format": "pcm" + "format": "pcm", + "supported_formats": [ "pcm" ] } ] } @@ -453,6 +457,7 @@ curl -X GET "http://localhost:3689/api/outputs/0" "needs_auth_key": false, "volume": 3 "format": "pcm", + "supported_formats": [ "pcm" ] } ``` diff --git a/src/cache.c b/src/cache.c index f61a7001..9dc62067 100644 --- a/src/cache.c +++ b/src/cache.c @@ -37,8 +37,9 @@ #include "conffile.h" #include "logger.h" -#include "httpd.h" +#include "httpd.h" // TODO get rid of this, only used for httpd_gzip_deflate #include "httpd_daap.h" +#include "transcode.h" #include "db.h" #include "cache.h" #include "listener.h" @@ -189,6 +190,7 @@ static struct cache_db_def cache_artwork_db_def[] = { static sqlite3 *cache_xcode_hdl; static struct event *cache_xcode_updateev; static struct event *cache_xcode_prepareev; +static bool cache_xcode_is_enabled; static int cache_xcode_last_file; static struct cache_db_def cache_xcode_db_def[] = { DB_DEF_ADMIN, @@ -897,6 +899,20 @@ xcode_header_get(void *arg, int *retval) #undef Q_TMPL } +static enum command_state +xcode_toggle(void *arg, int *retval) +{ + bool *enable = arg; + + cache_xcode_is_enabled = *enable; + + if (cache_xcode_is_enabled) + event_active(cache_xcode_updateev, 0, 0); + + *retval = 0; + return COMMAND_END; +} + static int xcode_add_entry(sqlite3 *hdl, uint32_t id, uint32_t ts, const char *path) { @@ -982,8 +998,6 @@ xcode_sync_with_files(sqlite3 *hdl) int i; int ret; - DPRINTF(E_INFO, L_CACHE, "Beginning transcode sync\n"); - // Both lists must be sorted by id, otherwise the compare below won't work ret = sqlite3_prepare_v2(hdl, "SELECT id, time_modified FROM files ORDER BY id;", -1, &stmt, 0); if (ret != SQLITE_OK) @@ -1044,8 +1058,6 @@ xcode_sync_with_files(sqlite3 *hdl) } db_query_end(&qp); - DPRINTF(E_INFO, L_CACHE, "Transcode sync completed\n"); - free(cachelist); return 0; @@ -1068,7 +1080,12 @@ xcode_prepare_header(sqlite3 *hdl, const char *format, int id, const char *path) DPRINTF(E_DBG, L_CACHE, "Preparing %s header for '%s' (file id %d)\n", format, path, id); #if 1 - ret = httpd_prepare_header(&header, format, path); // Proceed even if error, we also cache that + if (strcmp(format, "mp4") == 0) + ret = transcode_prepare_header(&header, XCODE_MP4_ALAC, path); + else + ret = -1; + + // Proceed even if error, we also cache that if (ret == 0) { datalen = evbuffer_get_length(header); @@ -1202,7 +1219,7 @@ cache_database_update(void *arg, int *retval) // TODO unlink or rename cache.db -// if (prefer_format && strcmp(prefer_format, "alac")) // TODO Ugly + if (cache_xcode_is_enabled) event_add(cache_xcode_updateev, &delay_xcode); *retval = 0; @@ -1731,6 +1748,15 @@ cache_xcode_header_get(struct evbuffer *evbuf, int *cached, uint32_t id, const c return ret; } +int +cache_xcode_toggle(bool enable) +{ + if (!cache_is_initialized) + return -1; + + return commands_exec_sync(cmdbase, xcode_toggle, NULL, &enable); +} + /* ---------------------------- Artwork cache API -------------------------- */ diff --git a/src/cache.h b/src/cache.h index 5a0ccf4c..aefe5502 100644 --- a/src/cache.h +++ b/src/cache.h @@ -27,6 +27,10 @@ cache_daap_threshold_get(void); int cache_xcode_header_get(struct evbuffer *evbuf, int *cached, uint32_t id, const char *format); +int +cache_xcode_toggle(bool enable); + + /* ---------------------------- Artwork cache API -------------------------- */ #define CACHE_ARTWORK_GROUP 0 diff --git a/src/db.c b/src/db.c index 8a78c631..8b23af16 100644 --- a/src/db.c +++ b/src/db.c @@ -4790,7 +4790,7 @@ db_speaker_save(struct output_device *device) #define Q_TMPL "INSERT OR REPLACE INTO speakers (id, selected, volume, name, auth_key, format) VALUES (%" PRIi64 ", %d, %d, %Q, %Q, %d);" char *query; - query = sqlite3_mprintf(Q_TMPL, device->id, device->selected, device->volume, device->name, device->auth_key, device->format); + query = sqlite3_mprintf(Q_TMPL, device->id, device->selected, device->volume, device->name, device->auth_key, device->selected_format); return db_query_run(query, 1, 0); #undef Q_TMPL @@ -4841,7 +4841,7 @@ db_speaker_get(struct output_device *device, uint64_t id) free(device->auth_key); device->auth_key = safe_strdup((char *)sqlite3_column_text(stmt, 3)); - device->format = sqlite3_column_int(stmt, 4); + device->selected_format = sqlite3_column_int(stmt, 4); #ifdef DB_PROFILE while (db_blocking_step(stmt) == SQLITE_ROW) diff --git a/src/httpd.c b/src/httpd.c index 07527ed7..b54d8dba 100644 --- a/src/httpd.c +++ b/src/httpd.c @@ -49,6 +49,8 @@ #include "httpd_internal.h" #include "transcode.h" #include "cache.h" +#include "listener.h" +#include "player.h" #ifdef LASTFM # include "lastfm.h" #endif @@ -905,6 +907,39 @@ stream_fail_cb(void *arg) } +/* -------------------------- SPEAKER/CACHE HANDLING ------------------------ */ + +// Thread: player (must not block) +static void +speaker_enum_cb(struct player_speaker_info *spk, void *arg) +{ + bool *want_mp4 = arg; + + *want_mp4 = *want_mp4 || (spk->format == MEDIA_FORMAT_ALAC && strcmp(spk->output_type, "RCP/SoundBridge") == 0); +} + +// Thread: worker +static void +speaker_update_handler_cb(void *arg) +{ + const char *prefer_format = cfg_getstr(cfg_getsec(cfg, "library"), "prefer_format"); + bool want_mp4; + + want_mp4 = (prefer_format && strcmp(prefer_format, "alac")); + if (!want_mp4) + player_speaker_enumerate(speaker_enum_cb, &want_mp4); + + cache_xcode_toggle(want_mp4); +} + +// Thread: player (must not block) +static void +httpd_speaker_update_handler(short event_mask) +{ + worker_execute(speaker_update_handler_cb, NULL, 0, 0); +} + + /* ---------------------------- REQUEST CALLBACKS --------------------------- */ // Worker thread, invoked by request_cb() below @@ -1220,15 +1255,6 @@ httpd_gzip_deflate(struct evbuffer *in) return NULL; } -int -httpd_prepare_header(struct evbuffer **header, const char *format, const char *path) -{ - if (strcmp(format, "mp4") == 0) - return transcode_prepare_header(header, XCODE_MP4_ALAC, path); - else - return -1; -} - // The httpd_send functions below can be called from a worker thread (with // hreq->is_async) or directly from the httpd thread. In the former case, they // will command sending from the httpd thread, since it is not safe to access @@ -1543,6 +1569,10 @@ httpd_init(const char *webroot) goto error; } + // We need to know about speaker format changes so we can ask the cache to + // start preparing headers for mp4/alac if selected + listener_add(httpd_speaker_update_handler, LISTENER_SPEAKER); + return 0; error: @@ -1554,6 +1584,8 @@ httpd_init(const char *webroot) void httpd_deinit(void) { + listener_remove(httpd_speaker_update_handler); + // Give modules a chance to hang up connections nicely modules_deinit(); diff --git a/src/httpd.h b/src/httpd.h index 0fed50c6..8926f707 100644 --- a/src/httpd.h +++ b/src/httpd.h @@ -13,17 +13,6 @@ struct evbuffer * httpd_gzip_deflate(struct evbuffer *in); -/* - * Passthrough to transcode, which will create a transcoded file header for path - * - * @out header Newly created evbuffer with the header - * @in format Which format caller wants a header for - * @in path Path to the file - * @return 0 if ok, otherwise -1 - */ -int -httpd_prepare_header(struct evbuffer **header, const char *format, const char *path); - int httpd_init(const char *webroot); diff --git a/src/httpd_jsonapi.c b/src/httpd_jsonapi.c index 5e9de857..09984541 100644 --- a/src/httpd_jsonapi.c +++ b/src/httpd_jsonapi.c @@ -1522,48 +1522,23 @@ struct outputs_param int output_volume; }; -static enum player_format -plformat_from_string(const char *format) -{ - if (strcmp(format, "pcm") == 0) - return PLAYER_FORMAT_PCM; - if (strcmp(format, "wav") == 0) - return PLAYER_FORMAT_WAV; - if (strcmp(format, "mp3") == 0) - return PLAYER_FORMAT_MP3; - if (strcmp(format, "alac") == 0) - return PLAYER_FORMAT_ALAC; - if (strcmp(format, "opus") == 0) - return PLAYER_FORMAT_OPUS; - - return PLAYER_FORMAT_UNKNOWN; -} - -static const char * -plformat_to_string(enum player_format format) -{ - if (format == PLAYER_FORMAT_PCM) - return "pcm"; - if (format == PLAYER_FORMAT_WAV) - return "wav"; - if (format == PLAYER_FORMAT_MP3) - return "mp3"; - if (format == PLAYER_FORMAT_ALAC) - return "alac"; - if (format == PLAYER_FORMAT_OPUS) - return "opus"; - - return "unknown"; -} - static json_object * speaker_to_json(struct player_speaker_info *spk) { json_object *output; + json_object *supported_formats; char output_id[21]; + enum media_format format; output = json_object_new_object(); + supported_formats = json_object_new_array(); + for (format = MEDIA_FORMAT_FIRST; format <= MEDIA_FORMAT_LAST; format = MEDIA_FORMAT_NEXT(format)) + { + if (format & spk->supported_formats) + json_object_array_add(supported_formats, json_object_new_string(media_format_to_string(format))); + } + snprintf(output_id, sizeof(output_id), "%" PRIu64, spk->id); json_object_object_add(output, "id", json_object_new_string(output_id)); json_object_object_add(output, "name", json_object_new_string(spk->name)); @@ -1573,7 +1548,8 @@ speaker_to_json(struct player_speaker_info *spk) json_object_object_add(output, "requires_auth", json_object_new_boolean(spk->requires_auth)); json_object_object_add(output, "needs_auth_key", json_object_new_boolean(spk->needs_auth_key)); json_object_object_add(output, "volume", json_object_new_int(spk->absvol)); - json_object_object_add(output, "format", json_object_new_string(plformat_to_string(spk->format))); + json_object_object_add(output, "format", json_object_new_string(media_format_to_string(spk->format))); + json_object_object_add(output, "supported_formats", supported_formats); return output; } @@ -1684,13 +1660,13 @@ jsonapi_reply_outputs_put_byid(struct httpd_request *hreq) { format = jparse_str_from_obj(request, "format"); if (format) - ret = player_speaker_format_set(output_id, plformat_from_string(format)); + ret = player_speaker_format_set(output_id, media_format_from_string(format)); } jparse_free(request); if (ret < 0) - return HTTP_INTERNAL; + return HTTP_BADREQUEST; return HTTP_NOCONTENT; } diff --git a/src/httpd_streaming.c b/src/httpd_streaming.c index 7590ca25..92d170b3 100644 --- a/src/httpd_streaming.c +++ b/src/httpd_streaming.c @@ -228,7 +228,7 @@ session_free(struct streaming_session *session) } static struct streaming_session * -session_new(struct httpd_request *hreq, bool icy_is_requested, enum player_format format, struct media_quality quality) +session_new(struct httpd_request *hreq, bool icy_is_requested, enum media_format format, struct media_quality quality) { struct streaming_session *session; int audio_fd; @@ -279,7 +279,7 @@ streaming_mp3_handler(struct httpd_request *hreq) httpd_header_add(hreq->out_headers, "icy-metaint", buf); } - session = session_new(hreq, icy_is_requested, PLAYER_FORMAT_MP3, streaming_default_quality); + session = session_new(hreq, icy_is_requested, MEDIA_FORMAT_MP3, streaming_default_quality); if (!session) return -1; // Error sent by caller diff --git a/src/misc.c b/src/misc.c index f662f3d1..921d990d 100644 --- a/src/misc.c +++ b/src/misc.c @@ -1731,6 +1731,40 @@ quality_is_equal(struct media_quality *a, struct media_quality *b) return (a->sample_rate == b->sample_rate && a->bits_per_sample == b->bits_per_sample && a->channels == b->channels && a->bit_rate == b->bit_rate); } +enum media_format +media_format_from_string(const char *s) +{ + if (strcmp(s, "pcm") == 0) + return MEDIA_FORMAT_PCM; + if (strcmp(s, "wav") == 0) + return MEDIA_FORMAT_WAV; + if (strcmp(s, "mp3") == 0) + return MEDIA_FORMAT_MP3; + if (strcmp(s, "alac") == 0) + return MEDIA_FORMAT_ALAC; + if (strcmp(s, "opus") == 0) + return MEDIA_FORMAT_OPUS; + + return MEDIA_FORMAT_UNKNOWN; +} + +const char * +media_format_to_string(enum media_format format) +{ + if (format == MEDIA_FORMAT_PCM) + return "pcm"; + if (format == MEDIA_FORMAT_WAV) + return "wav"; + if (format == MEDIA_FORMAT_MP3) + return "mp3"; + if (format == MEDIA_FORMAT_ALAC) + return "alac"; + if (format == MEDIA_FORMAT_OPUS) + return "opus"; + + return "unknown"; +} + /* -------------------------- Misc utility functions ------------------------ */ diff --git a/src/misc.h b/src/misc.h index e3d1b7d9..fe5615a5 100644 --- a/src/misc.h +++ b/src/misc.h @@ -272,6 +272,21 @@ timespec_reltoabs(struct timespec relative); /* ------------------------------- Media quality ---------------------------- */ +// Bit flags for the sake of outputs announcing what they support +enum media_format { + MEDIA_FORMAT_UNKNOWN = 0, + MEDIA_FORMAT_PCM = (1 << 0), + MEDIA_FORMAT_WAV = (1 << 1), + MEDIA_FORMAT_MP3 = (1 << 2), + MEDIA_FORMAT_ALAC = (1 << 3), + MEDIA_FORMAT_OPUS = (1 << 4), +}; + +// For iteration +#define MEDIA_FORMAT_FIRST MEDIA_FORMAT_PCM +#define MEDIA_FORMAT_LAST MEDIA_FORMAT_OPUS +#define MEDIA_FORMAT_NEXT(f) (f << 1) + // Remember to adjust quality_is_equal() if adding elements struct media_quality { int sample_rate; @@ -283,6 +298,12 @@ struct media_quality { bool quality_is_equal(struct media_quality *a, struct media_quality *b); +enum media_format +media_format_from_string(const char *s); + +const char * +media_format_to_string(enum media_format format); + /* -------------------------- Misc utility functions ------------------------ */ diff --git a/src/outputs.h b/src/outputs.h index 61d380c1..eb1def82 100644 --- a/src/outputs.h +++ b/src/outputs.h @@ -134,7 +134,11 @@ struct output_device // Quality of audio output struct media_quality quality; - int format; + + // selected_format only set (not UNKNOWN) in case of active user selection + enum media_format selected_format; + enum media_format default_format; + uint32_t supported_formats; // Address char *v4_address; diff --git a/src/outputs/airplay.c b/src/outputs/airplay.c index 7e6b3cf7..c18b4625 100644 --- a/src/outputs/airplay.c +++ b/src/outputs/airplay.c @@ -3737,6 +3737,7 @@ airplay_device_cb(const char *name, const char *type, const char *domain, const rd->type = OUTPUT_TYPE_AIRPLAY; rd->type_name = outputs_name(rd->type); rd->extra_device_info = re; + rd->supported_formats = MEDIA_FORMAT_ALAC; if (port < 0) { diff --git a/src/outputs/alsa.c b/src/outputs/alsa.c index 8e27027c..37fdb902 100644 --- a/src/outputs/alsa.c +++ b/src/outputs/alsa.c @@ -1374,6 +1374,7 @@ alsa_device_add(cfg_t* cfg_audio, int id) device->type = OUTPUT_TYPE_ALSA; device->type_name = outputs_name(device->type); device->extra_device_info = ae; + device->supported_formats = MEDIA_FORMAT_PCM; // The audio section will have no title, so there we get the value from the // "card" option diff --git a/src/outputs/cast.c b/src/outputs/cast.c index b6cc5ffb..80cc1494 100644 --- a/src/outputs/cast.c +++ b/src/outputs/cast.c @@ -1778,6 +1778,7 @@ cast_device_cb(const char *name, const char *type, const char *domain, const cha device->name = strdup(name); device->type = OUTPUT_TYPE_CAST; device->type_name = outputs_name(device->type); + device->supported_formats = MEDIA_FORMAT_OPUS; if (port < 0) { diff --git a/src/outputs/fifo.c b/src/outputs/fifo.c index b87510b6..ae2f6e41 100644 --- a/src/outputs/fifo.c +++ b/src/outputs/fifo.c @@ -491,6 +491,7 @@ fifo_init(void) device->type_name = outputs_name(device->type); device->has_video = 0; device->extra_device_info = path; + device->supported_formats = MEDIA_FORMAT_PCM; DPRINTF(E_INFO, L_FIFO, "Adding fifo output device '%s' with path '%s'\n", nickname, path); player_device_add(device); diff --git a/src/outputs/pulse.c b/src/outputs/pulse.c index cd3ea95e..c110870f 100644 --- a/src/outputs/pulse.c +++ b/src/outputs/pulse.c @@ -436,6 +436,7 @@ sinklist_cb(pa_context *ctx, const pa_sink_info *info, int eol, void *userdata) device->type = OUTPUT_TYPE_PULSE; device->type_name = outputs_name(device->type); device->extra_device_info = strdup(info->name); + device->supported_formats = MEDIA_FORMAT_PCM; player_device_add(device); } diff --git a/src/outputs/raop.c b/src/outputs/raop.c index 20436aaa..f37a6b7d 100644 --- a/src/outputs/raop.c +++ b/src/outputs/raop.c @@ -4230,6 +4230,7 @@ raop_device_cb(const char *name, const char *type, const char *domain, const cha rd->type = OUTPUT_TYPE_RAOP; rd->type_name = outputs_name(rd->type); rd->extra_device_info = re; + rd->supported_formats = MEDIA_FORMAT_ALAC; if (port < 0) { diff --git a/src/outputs/rcp.c b/src/outputs/rcp.c index ebae9da7..212a6897 100644 --- a/src/outputs/rcp.c +++ b/src/outputs/rcp.c @@ -1295,6 +1295,8 @@ rcp_mdns_device_cb(const char *name, const char *type, const char *domain, const device->name = strdup(name); device->type = OUTPUT_TYPE_RCP; device->type_name = outputs_name(device->type); + device->default_format = MEDIA_FORMAT_WAV; + device->supported_formats = MEDIA_FORMAT_WAV | MEDIA_FORMAT_MP3 | MEDIA_FORMAT_ALAC; if (port < 0 || !address) { diff --git a/src/outputs/streaming.c b/src/outputs/streaming.c index ecb5314b..11c43234 100644 --- a/src/outputs/streaming.c +++ b/src/outputs/streaming.c @@ -65,7 +65,7 @@ struct streaming_wanted struct pipepair audio[WANTED_PIPES_MAX]; struct pipepair metadata[WANTED_PIPES_MAX]; - enum player_format format; + enum media_format format; struct media_quality quality; struct evbuffer *audio_in; @@ -113,7 +113,7 @@ extern struct event_base *evbase_player; /* ------------------------------- Helpers ---------------------------------- */ static struct encode_ctx * -encoder_setup(enum player_format format, struct media_quality *quality) +encoder_setup(enum media_format format, struct media_quality *quality) { struct transcode_encode_setup_args encode_args = { .profile = XCODE_MP3, .quality = quality }; struct encode_ctx *encode_ctx = NULL; @@ -132,7 +132,7 @@ encoder_setup(enum player_format format, struct media_quality *quality) goto out; } - if (format == PLAYER_FORMAT_MP3) + if (format == MEDIA_FORMAT_MP3) encode_ctx = transcode_encode_setup(encode_args); if (!encode_ctx) @@ -217,7 +217,7 @@ pipe_index_find_byreadfd(struct pipepair *p, int readfd) } static struct streaming_wanted * -wanted_new(enum player_format format, struct media_quality quality) +wanted_new(enum media_format format, struct media_quality quality) { struct streaming_wanted *w; @@ -277,7 +277,7 @@ wanted_remove(struct streaming_wanted **wanted, struct streaming_wanted *remove) } static struct streaming_wanted * -wanted_add(struct streaming_wanted **wanted, enum player_format format, struct media_quality quality) +wanted_add(struct streaming_wanted **wanted, enum media_format format, struct media_quality quality) { struct streaming_wanted *w; @@ -289,7 +289,7 @@ wanted_add(struct streaming_wanted **wanted, enum player_format format, struct m } static struct streaming_wanted * -wanted_find_byformat(struct streaming_wanted *wanted, enum player_format format, struct media_quality quality) +wanted_find_byformat(struct streaming_wanted *wanted, enum media_format format, struct media_quality quality) { struct streaming_wanted *w; @@ -623,9 +623,9 @@ streaming_start(struct output_device *device, int callback_id) int ret; pthread_mutex_lock(&streaming_wanted_lck); - w = wanted_find_byformat(streaming.wanted, device->format, device->quality); + w = wanted_find_byformat(streaming.wanted, device->selected_format, device->quality); if (!w) - w = wanted_add(&streaming.wanted, device->format, device->quality); + w = wanted_add(&streaming.wanted, device->selected_format, device->quality); ret = wanted_session_add(&device->audio_fd, &device->metadata_fd, w); if (ret < 0) goto error; diff --git a/src/player.c b/src/player.c index 96975dca..3e11a96c 100644 --- a/src/player.c +++ b/src/player.c @@ -152,7 +152,7 @@ struct speaker_attr_param bool busy; struct media_quality quality; - enum player_format format; + enum media_format format; int audio_fd; int metadata_fd; @@ -2526,7 +2526,15 @@ device_to_speaker_info(struct player_speaker_info *spk, struct output_device *de spk->output_type[sizeof(spk->output_type) - 1] = '\0'; spk->relvol = device->relvol; spk->absvol = device->volume; - spk->format = device->format; + + spk->supported_formats = device->supported_formats; + // Devices supporting more than one format should at least have default_format set + if (device->selected_format != MEDIA_FORMAT_UNKNOWN) + spk->format = device->selected_format; + else if (device->default_format != MEDIA_FORMAT_UNKNOWN) + spk->format = device->default_format; + else + spk->format = device->supported_formats; spk->selected = OUTPUTS_DEVICE_DISPLAY_SELECTED(device); @@ -2814,19 +2822,25 @@ speaker_format_set(void *arg, int *retval) struct speaker_attr_param *param = arg; struct output_device *device; - *retval = -1; - - if (param->format == PLAYER_FORMAT_UNKNOWN) - return COMMAND_END; + if (param->format == MEDIA_FORMAT_UNKNOWN) + goto error; device = outputs_device_get(param->spk_id); if (!device) - return COMMAND_END; + goto error; - device->format = param->format; + if (!(param->format & device->supported_formats)) + goto error; + + device->selected_format = param->format; *retval = 0; return COMMAND_END; + + error: + DPRINTF(E_LOG, L_PLAYER, "Error setting format '%s', device unknown or format unsupported\n", media_format_to_string(param->format)); + *retval = -1; + return COMMAND_END; } // Attempts to reactivate a speaker that has failed. That includes restarting @@ -2933,7 +2947,7 @@ streaming_register(void *arg, int *retval) .type_name = "streaming", .name = "streaming", .quality = param->quality, - .format = param->format, + .selected_format = param->format, }; *retval = outputs_device_start(&device, NULL, false); @@ -3500,18 +3514,18 @@ player_speaker_authorize(uint64_t id, const char *pin) } int -player_speaker_format_set(uint64_t id, enum player_format format) +player_speaker_format_set(uint64_t id, enum media_format format) { struct speaker_attr_param param; param.spk_id = id; param.format = format; - return commands_exec_sync(cmdbase, speaker_format_set, NULL, ¶m); + return commands_exec_sync(cmdbase, speaker_format_set, speaker_generic_bh, ¶m); } int -player_streaming_register(int *audio_fd, int *metadata_fd, enum player_format format, struct media_quality quality) +player_streaming_register(int *audio_fd, int *metadata_fd, enum media_format format, struct media_quality quality) { struct speaker_attr_param param; int ret; diff --git a/src/player.h b/src/player.h index 8aed0aae..3f7867a8 100644 --- a/src/player.h +++ b/src/player.h @@ -28,15 +28,6 @@ enum player_seek_mode { PLAYER_SEEK_RELATIVE = 2, }; -enum player_format { - PLAYER_FORMAT_UNKNOWN = -1, - PLAYER_FORMAT_PCM = 0, - PLAYER_FORMAT_WAV = 1, - PLAYER_FORMAT_MP3 = 2, - PLAYER_FORMAT_ALAC = 3, - PLAYER_FORMAT_OPUS = 4, -}; - struct player_speaker_info { uint64_t id; uint32_t active_remote; @@ -45,7 +36,8 @@ struct player_speaker_info { int relvol; int absvol; - enum player_format format; + enum media_format format; + uint32_t supported_formats; bool selected; bool has_password; @@ -130,10 +122,10 @@ int player_speaker_authorize(uint64_t id, const char *pin); int -player_speaker_format_set(uint64_t id, enum player_format format); +player_speaker_format_set(uint64_t id, enum media_format format); int -player_streaming_register(int *audio_fd, int *metadata_fd, enum player_format format, struct media_quality quality); +player_streaming_register(int *audio_fd, int *metadata_fd, enum media_format format, struct media_quality quality); int player_streaming_deregister(int id);