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];
*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':
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);
}
}
}
+ 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,
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);
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);
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);
}
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;
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
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;
}
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;
}
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;
}
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)) {
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;
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;
uint32_t songtime_in_milliseconds;
int songtime_in_milliseconds_changed;
+ int songtime_in_milliseconds_is_valid;
+
// end
}
// 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);
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
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':
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;
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;
<property name='ProgressString' type='s' access='read'/>
<property name='Client' type='s' access='read'/>
<property name='ClientName' type='s' access='read'/>
+ <property name='StreamType' type='s' access='read'/>
<property name='AirplayVolume' type='d' access='read'/>
<method name="SetAirplayVolume">
<arg name="volume" type="d" direction="in" />
#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;
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;
#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