From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Sat, 12 Nov 2022 16:42:25 +0000 (+0000) Subject: Add new metadata item: sps:songdatakind, derived from the asdk metadata token. It... X-Git-Tag: 4.1.1~2^2~38 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d67909b837feb9367f0731b70017c8fecf3ec5d9;p=thirdparty%2Fshairport-sync.git Add new metadata item: sps:songdatakind, derived from the asdk metadata token. It seems to indicate a timed item (0) or an untimed stream (1). Add output format, output frame rate, stream type (Realtime/Buffered/Classic) properties. Update MQTT appropriately. --- diff --git a/dacp.c b/dacp.c index 73164ae2..d557de24 100644 --- a/dacp.c +++ b/dacp.c @@ -803,8 +803,8 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) { case 'canp': // nowplaying 4 ids: dbid, plid, playlistItem, itemid (from mellowware // see reference above) debug(2, "DACP Composite ID seen"); - if (memcmp(metadata_store.item_composite_id, sp - item_size, - sizeof(metadata_store.item_composite_id)) != 0) { + if ((metadata_store.item_composite_id_is_valid == 0) || (memcmp(metadata_store.item_composite_id, sp - item_size, + sizeof(metadata_store.item_composite_id)) != 0)) { memcpy(metadata_store.item_composite_id, sp - item_size, sizeof(metadata_store.item_composite_id)); char st[33]; @@ -817,6 +817,7 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) { *pt = 0; debug(2, "Item composite ID changed to 0x%s.", st); metadata_store.item_composite_id_changed = 1; + metadata_store.item_composite_id_is_valid = 1; } break; case 'astm': @@ -826,6 +827,7 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) { if (ui != metadata_store.songtime_in_milliseconds) { metadata_store.songtime_in_milliseconds = ui; metadata_store.songtime_in_milliseconds_changed = 1; + metadata_store.songtime_in_milliseconds_is_valid = 1; debug(2, "DACP Song Time set to: \"%u\"", metadata_store.songtime_in_milliseconds); } diff --git a/dbus-service.c b/dbus-service.c index b8cfea16..51c1b128 100644 --- a/dbus-service.c +++ b/dbus-service.c @@ -96,6 +96,18 @@ void dbus_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused)) } } + if (argc->stream_type) { + // debug(1, "Check stream type"); + th = shairport_sync_remote_control_get_stream_type(shairportSyncRemoteControlSkeleton); + if ((th == NULL) || (strcasecmp(th, argc->stream_type) != 0)) { + // debug(1, "Stream type string should be changed"); + shairport_sync_remote_control_set_stream_type(shairportSyncRemoteControlSkeleton, + argc->stream_type); + } + } + + + switch (argc->player_state) { case PS_NOT_AVAILABLE: shairport_sync_remote_control_set_player_state(shairportSyncRemoteControlSkeleton, @@ -202,8 +214,8 @@ void dbus_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused)) g_variant_builder_add(dict_builder, "{sv}", "mpris:artUrl", artUrl); } - // Add in the Track ID based on the 'mper' metadata if it is non-zero - if (argc->item_id != 0) { + // Add in the Track ID based on the 'mper' metadata if it is valid + if (argc->item_id_is_valid != 0) { char trackidstring[128]; snprintf(trackidstring, sizeof(trackidstring), "/org/gnome/ShairportSync/%" PRIX64 "", argc->item_id); @@ -211,6 +223,14 @@ void dbus_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused)) g_variant_builder_add(dict_builder, "{sv}", "mpris:trackid", trackid); } + // Add in the Song Data Kind based on the 'asdk' metadata if it is valid + // It seems that this is 0 for a timed play, e.g. a track or an album, but is 1 for an untimed play, such as a stream. + + if (argc->song_data_kind_is_valid != 0) { + GVariant *songdatakind = g_variant_new_uint32(argc->song_data_kind); + g_variant_builder_add(dict_builder, "{sv}", "sps:songdatakind", songdatakind); + } + // Add the track name if it exists if (argc->track_name) { GVariant *track_name = g_variant_new("s", argc->track_name); @@ -241,11 +261,11 @@ void dbus_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused)) g_variant_builder_add(dict_builder, "{sv}", "xesam:genre", genre); } - if (argc->songtime_in_milliseconds) { + if (argc->songtime_in_milliseconds_is_valid != 0) { uint64_t track_length_in_microseconds = argc->songtime_in_milliseconds; track_length_in_microseconds *= 1000; // to microseconds in 64-bit precision // Make up the track name and album name - // debug(1, "Set tracklength to %lu.", track_length_in_microseconds); + // debug(1, "Set tracklength to %" PRId64 ".", track_length_in_microseconds); GVariant *tracklength = g_variant_new("x", track_length_in_microseconds); g_variant_builder_add(dict_builder, "{sv}", "mpris:length", tracklength); } diff --git a/metadata_hub.c b/metadata_hub.c index 9ad7052d..29190f90 100644 --- a/metadata_hub.c +++ b/metadata_hub.c @@ -110,6 +110,7 @@ void run_metadata_watchers(void) { metadata_store.artist_name_changed = 0; metadata_store.album_artist_name_changed = 0; metadata_store.album_name_changed = 0; + metadata_store.song_data_kind_changed = 0; metadata_store.track_name_changed = 0; metadata_store.genre_changed = 0; metadata_store.comment_changed = 0; @@ -348,6 +349,19 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin char *cs; if (type == 'core') { switch (code) { + case 'asdk': { + // get the one-byte number as an unsigned number + int song_data_kind = data[0]; // one byte + song_data_kind = song_data_kind & 0xFF; // unsigned + debug(2, "MH Song Data Kind seen: \"%d\" of length %u.", song_data_kind, length); + if ((song_data_kind != metadata_store.song_data_kind) || (metadata_store.song_data_kind_is_valid == 0)) { + metadata_store.song_data_kind = song_data_kind; + metadata_store.song_data_kind_changed = 1; + metadata_store.song_data_kind_is_valid = 1; + debug(2, "MH Song Data Kind set to: \"%d\"", metadata_store.song_data_kind); + metadata_packet_item_changed = 1; + } + } break; case 'mper': { // get the 64-bit number as a uint64_t by reading two uint32_t s and combining them uint64_t vl = ntohl(*(uint32_t *)data); // get the high order 32 bits @@ -355,10 +369,10 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin uint64_t ul = ntohl(*(uint32_t *)(data + sizeof(uint32_t))); // and the low order 32 bits vl = vl + ul; debug(2, "MH Item ID seen: \"%" PRIx64 "\" of length %u.", vl, length); - if (vl != metadata_store.item_id) { + if ((vl != metadata_store.item_id) || (metadata_store.item_id_is_valid == 0)) { metadata_store.item_id = vl; metadata_store.item_id_changed = 1; - metadata_store.item_id_received = 1; + metadata_store.item_id_is_valid = 1; debug(2, "MH Item ID set to: \"%" PRIx64 "\"", metadata_store.item_id); metadata_packet_item_changed = 1; } @@ -366,9 +380,10 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin case 'astm': { uint32_t ui = ntohl(*(uint32_t *)data); debug(2, "MH Song Time seen: \"%u\" of length %u.", ui, length); - if (ui != metadata_store.songtime_in_milliseconds) { + if ((ui != metadata_store.songtime_in_milliseconds) || (metadata_store.songtime_in_milliseconds_is_valid == 0)) { metadata_store.songtime_in_milliseconds = ui; metadata_store.songtime_in_milliseconds_changed = 1; + metadata_store.songtime_in_milliseconds_is_valid = 1; debug(2, "MH Song Time set to: \"%u\"", metadata_store.songtime_in_milliseconds); metadata_packet_item_changed = 1; } @@ -554,7 +569,7 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin cs = strndup(data, length); if (string_update(&metadata_store.client_name, &metadata_store.client_name_changed, cs)) { changed = 1; - debug(1, "MH Client Name set to: \"%s\"", metadata_store.client_name); + debug(2, "MH Client Name set to: \"%s\"", metadata_store.client_name); } free(cs); break; @@ -567,6 +582,15 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin } free(cs); break; + case 'styp': + cs = strndup(data, length); + if (string_update(&metadata_store.stream_type, &metadata_store.stream_type_changed, + cs)) { + changed = 1; + debug(2, "MH Stream Type set to: \"%s\"", metadata_store.stream_type); + } + free(cs); + break; case 'svip': cs = strndup(data, length); if (string_update(&metadata_store.server_ip, &metadata_store.server_ip_changed, cs)) { diff --git a/metadata_hub.h b/metadata_hub.h index bb57a8d5..730e6e8c 100644 --- a/metadata_hub.h +++ b/metadata_hub.h @@ -48,6 +48,9 @@ typedef struct metadata_bundle { char *server_ip; // IP number used by Shairport Sync int server_ip_changed; + + char *stream_type; // Realtime or Buffered + int stream_type_changed; char *progress_string; // progress string, emitted by the source from time to time int progress_string_changed; @@ -70,15 +73,21 @@ typedef struct metadata_bundle { char *cover_art_pathname; int cover_art_pathname_changed; - + uint64_t item_id; // seems to be a track ID -- see itemid in DACP.c int item_id_changed; - int item_id_received; // important for deciding if the track information should be ignored. + int item_id_is_valid; unsigned char item_composite_id[16]; // seems to be nowplaying 4 ids: dbid, plid, playlistItem, itemid int item_composite_id_changed; + int item_composite_id_is_valid; + + int song_data_kind; + int song_data_kind_changed; + int song_data_kind_is_valid; + char *track_name; int track_name_changed; @@ -123,6 +132,8 @@ typedef struct metadata_bundle { uint32_t songtime_in_milliseconds; int songtime_in_milliseconds_changed; + int songtime_in_milliseconds_is_valid; + // end diff --git a/mpris-service.c b/mpris-service.c index 8416df95..b31d179d 100644 --- a/mpris-service.c +++ b/mpris-service.c @@ -174,7 +174,7 @@ void mpris_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused) } // Add in the Track ID based on the 'mper' metadata if it is non-zero - if (argc->item_id != 0) { + if (argc->item_id_is_valid != 0) { char trackidstring[128]; snprintf(trackidstring, sizeof(trackidstring), "/org/gnome/ShairportSync/%" PRIX64 "", argc->item_id); @@ -212,7 +212,7 @@ void mpris_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused) g_variant_builder_add(dict_builder, "{sv}", "xesam:genre", genre); } - if (argc->songtime_in_milliseconds) { + if (argc->songtime_in_milliseconds_is_valid) { uint64_t track_length_in_microseconds = argc->songtime_in_milliseconds; track_length_in_microseconds *= 1000; // to microseconds in 64-bit precision // Make up the track name and album name diff --git a/mqtt.c b/mqtt.c index b12eb97d..cb426bdf 100644 --- a/mqtt.c +++ b/mqtt.c @@ -178,7 +178,10 @@ void mqtt_process_metadata(uint32_t type, uint32_t code, char *data, uint32_t le case 'asal': mqtt_publish("songalbum", data, length); break; - case 'clip': + case 'asdk': + mqtt_publish("songdatakind", data, length); // 0 seem to be a timed item, 1 an untimed stream + break; + case 'clip': mqtt_publish("client_ip", data, length); break; case 'cdid': @@ -193,6 +196,12 @@ void mqtt_process_metadata(uint32_t type, uint32_t code, char *data, uint32_t le case 'daid': mqtt_publish("dacp_id", data, length); break; + case 'ofmt': + mqtt_publish("output_format", data, length); + break; + case 'ofps': + mqtt_publish("output_frame_rate", data, length); + break; case 'pbeg': mqtt_publish("play_start", data, length); break; @@ -216,6 +225,9 @@ void mqtt_process_metadata(uint32_t type, uint32_t code, char *data, uint32_t le case 'snam': mqtt_publish("client_name", data, length); break; + case 'styp': + mqtt_publish("stream_type", data, length); + break; case 'svip': mqtt_publish("server_ip", data, length); break; diff --git a/org.gnome.ShairportSync.xml b/org.gnome.ShairportSync.xml index c9f5868f..47aa3537 100644 --- a/org.gnome.ShairportSync.xml +++ b/org.gnome.ShairportSync.xml @@ -51,6 +51,7 @@ + diff --git a/player.c b/player.c index 5aad9153..c6432eec 100644 --- a/player.c +++ b/player.c @@ -363,20 +363,32 @@ static void terminate_decoders(rtsp_conn_info *conn) { #endif } +uint64_t buffers_allocated = 0; +uint64_t buffers_released = 0; static void init_buffer(rtsp_conn_info *conn) { // debug(1,"input_bytes_per_frame: %d.", conn->input_bytes_per_frame); // debug(1,"input_bit_depth: %d.", conn->input_bit_depth); int i; - for (i = 0; i < BUFFER_FRAMES; i++) + for (i = 0; i < BUFFER_FRAMES; i++) { // conn->audio_buffer[i].data = malloc(conn->input_bytes_per_frame * // conn->max_frames_per_packet); - conn->audio_buffer[i].data = malloc(8 * conn->max_frames_per_packet); // todo + void *allocation = malloc(8 * conn->max_frames_per_packet); + if (allocation == NULL) { + die("could not allocate memory for audio buffers. %" PRId64 " buffers allocated, %" PRId64 " buffers released.", buffers_allocated, buffers_released); + } else { + conn->audio_buffer[i].data = allocation; + buffers_allocated++; + } + } + debug(1, "%" PRId64 " buffers allocated, %" PRId64 " buffers released.", buffers_allocated, buffers_released); } static void free_audio_buffers(rtsp_conn_info *conn) { int i; - for (i = 0; i < BUFFER_FRAMES; i++) + for (i = 0; i < BUFFER_FRAMES; i++) { free(conn->audio_buffer[i].data); + buffers_released++; + } } int first_possibly_missing_frame = -1; @@ -2725,23 +2737,31 @@ void *player_thread_func(void *arg) { sync_error = 0; // say the error was fixed! } // since this is the first frame of audio, inform the user if requested... - if (config.statistics_requested) { + #ifdef CONFIG_AIRPLAY_2 - if (conn->airplay_stream_type == realtime_stream) { - if (conn->airplay_type == ap_1) - inform("Connection %d: Playback Started -- AirPlay 1 Compatible.", - conn->connection_number); - else - inform("Connection %d: Playback Started -- AirPlay 2 Realtime.", - conn->connection_number); + if (conn->airplay_stream_type == realtime_stream) { + if (conn->airplay_type == ap_1) { + send_ssnc_metadata('styp', "Classic", strlen("Classic"), 1); + if (config.statistics_requested) + inform("Connection %d: Playback Started at frame %" PRId64 " -- AirPlay 1 Compatible.", + conn->connection_number, inframe->given_timestamp); } else { - inform("Connection %d: Playback Started -- AirPlay 2 Buffered.", - conn->connection_number); + send_ssnc_metadata('styp', "Realtime", strlen("Realtime"), 1); + if (config.statistics_requested) + inform("Connection %d: Playback Started at frame %" PRId64 " -- AirPlay 2 Realtime.", + conn->connection_number, inframe->given_timestamp); } + } else { + send_ssnc_metadata('styp', "Buffered", strlen("Buffered"), 1); + if (config.statistics_requested) + inform("Connection %d: Playback Started at frame %" PRId64 " -- AirPlay 2 Buffered.", + conn->connection_number, inframe->given_timestamp); + } #else - inform("Connection %d: Playback Started -- AirPlay 1.", conn->connection_number); + send_ssnc_metadata('styp', "Classic", strlen("Classic"), 1); + if (config.statistics_requested) + inform("Connection %d: Playback Started at frame %" PRId64 " -- AirPlay 1.", conn->connection_number, inframe->given_timestamp); #endif - } } // not too sure if abs() is implemented for int64_t, so we'll do it manually int64_t abs_sync_error = sync_error; diff --git a/shairport.c b/shairport.c index 86c01e83..7083b99f 100644 --- a/shairport.c +++ b/shairport.c @@ -2539,6 +2539,13 @@ int main(int argc, char **argv) { #ifdef CONFIG_METADATA send_ssnc_metadata('svna', config.service_name, strlen(config.service_name), 1); + char buffer[256] = ""; + snprintf(buffer, sizeof(buffer), "%d", + config.output_rate); + send_ssnc_metadata('ofps', buffer, strlen(buffer), 1); + snprintf(buffer, sizeof(buffer), "%s", + sps_format_description_string(config.output_format)); + send_ssnc_metadata('ofmt', buffer, strlen(buffer), 1); #endif activity_monitor_start(); // not yet for AP2