Add full metadata and coverart support to the library
[deb_shairplay.git] / src / lib / raop_rtp.c
index 88f65c602af9820481d9b510d0b29c1a9bdd2dd9..77814ba08680528e9a2d96b9ee9d42d40792d16a 100644 (file)
@@ -22,6 +22,7 @@
 #include "raop.h"
 #include "raop_buffer.h"
 #include "netutils.h"
+#include "utils.h"
 #include "compat.h"
 #include "logger.h"
 
@@ -31,15 +32,28 @@ struct raop_rtp_s {
        logger_t *logger;
        raop_callbacks_t callbacks;
 
+       /* Buffer to handle all resends */
        raop_buffer_t *buffer;
 
+       /* Remote address as sockaddr */
+       struct sockaddr_storage remote_saddr;
+       socklen_t remote_saddr_len;
+
+       /* MUTEX LOCKED VARIABLES START */
        /* These variables only edited mutex locked */
        int running;
        int joined;
+
        float volume;
+       unsigned char *metadata;
+       int metadata_len;
+       unsigned char *coverart;
+       int coverart_len;
+
        int flush;
        thread_handle_t thread;
        mutex_handle_t run_mutex;
+       /* MUTEX LOCKED VARIABLES END */
 
        /* Remote control and timing ports */
        unsigned short control_rport;
@@ -53,18 +67,67 @@ struct raop_rtp_s {
        unsigned short timing_lport;
        unsigned short data_lport;
 
+       /* Initialized after the first control packet */
        struct sockaddr_storage control_saddr;
        socklen_t control_saddr_len;
        unsigned short control_seqnum;
 };
 
+static int
+raop_rtp_parse_remote(raop_rtp_t *raop_rtp, const char *remote)
+{
+       char *original;
+       char *current;
+       char *tmpstr;
+       int family;
+       int ret;
+
+       assert(raop_rtp);
+
+       current = original = strdup(remote);
+       if (!original) {
+               return -1;
+       }
+       tmpstr = utils_strsep(&current, " ");
+       if (strcmp(tmpstr, "IN")) {
+               free(original);
+               return -1;
+       }
+       tmpstr = utils_strsep(&current, " ");
+       if (!strcmp(tmpstr, "IP4") && current) {
+               family = AF_INET;
+       } else if (!strcmp(tmpstr, "IP6") && current) {
+               family = AF_INET6;
+       } else {
+               free(original);
+               return -1;
+       }
+       if (strstr(current, ":")) {
+               /* FIXME: iTunes sends IP4 even with an IPv6 address, does it mean something */
+               family = AF_INET6;
+       }
+       ret = netutils_parse_address(family, current,
+                                    &raop_rtp->remote_saddr,
+                                    sizeof(raop_rtp->remote_saddr));
+       if (ret < 0) {
+               free(original);
+               return -1;
+       }
+       raop_rtp->remote_saddr_len = ret;
+       free(original);
+       return 0;
+}
+
 raop_rtp_t *
-raop_rtp_init(logger_t *logger, raop_callbacks_t *callbacks, const char *fmtp,
-              const unsigned char *aeskey, const unsigned char *aesiv)
+raop_rtp_init(logger_t *logger, raop_callbacks_t *callbacks, const char *remote,
+              const char *fmtp, const unsigned char *aeskey, const unsigned char *aesiv)
 {
        raop_rtp_t *raop_rtp;
 
        assert(logger);
+       assert(callbacks);
+       assert(remote);
+       assert(fmtp);
 
        raop_rtp = calloc(1, sizeof(raop_rtp_t));
        if (!raop_rtp) {
@@ -77,6 +140,10 @@ raop_rtp_init(logger_t *logger, raop_callbacks_t *callbacks, const char *fmtp,
                free(raop_rtp);
                return NULL;
        }
+       if (raop_rtp_parse_remote(raop_rtp, remote) < 0) {
+               free(raop_rtp);
+               return NULL;
+       }
 
        raop_rtp->running = 0;
        raop_rtp->joined = 1;
@@ -94,6 +161,8 @@ raop_rtp_destroy(raop_rtp_t *raop_rtp)
 
                MUTEX_DESTROY(raop_rtp->run_mutex);
                raop_buffer_destroy(raop_rtp->buffer);
+               free(raop_rtp->metadata);
+               free(raop_rtp->coverart);
                free(raop_rtp);
        }
 }
@@ -150,11 +219,12 @@ raop_rtp_resend_callback(void *opaque, unsigned short seqnum, unsigned short cou
        unsigned short ourseqnum;
        struct sockaddr *addr;
        socklen_t addrlen;
+       int ret;
 
        addr = (struct sockaddr *)&raop_rtp->control_saddr;
        addrlen = raop_rtp->control_saddr_len;
 
-       logger_log(raop_rtp->logger, LOGGER_DEBUG, "Got resend request %d %d\n", seqnum, count);
+       logger_log(raop_rtp->logger, LOGGER_DEBUG, "Got resend request %d %d", seqnum, count);
        ourseqnum = raop_rtp->control_seqnum++;
 
        /* Fill the request buffer */
@@ -167,7 +237,82 @@ raop_rtp_resend_callback(void *opaque, unsigned short seqnum, unsigned short cou
        packet[6] = (count >> 8);
        packet[7] =  count;
 
-       sendto(raop_rtp->csock, (const char *)packet, sizeof(packet), 0, addr, addrlen);
+       ret = sendto(raop_rtp->csock, (const char *)packet, sizeof(packet), 0, addr, addrlen);
+       if (ret == -1) {
+               logger_log(raop_rtp->logger, LOGGER_WARNING, "Resend failed: %d", SOCKET_GET_ERROR());
+       }
+
+       return 0;
+}
+
+static int
+raop_rtp_process_events(raop_rtp_t *raop_rtp, void *cb_data, float *volume)
+{
+       int flush;
+       int volume_changed;
+       unsigned char *metadata;
+       int metadata_len;
+       unsigned char *coverart;
+       int coverart_len;
+
+       assert(raop_rtp);
+       assert(volume);
+
+       MUTEX_LOCK(raop_rtp->run_mutex);
+       if (!raop_rtp->running) {
+               MUTEX_UNLOCK(raop_rtp->run_mutex);
+               return 1;
+       }
+
+       /* Read the volume level */
+       volume_changed = (*volume != raop_rtp->volume);
+       *volume = raop_rtp->volume;
+
+       /* Read the flush value */
+       flush = raop_rtp->flush;
+       raop_rtp->flush = NO_FLUSH;
+
+       /* Read the metadata */
+       metadata = raop_rtp->metadata;
+       metadata_len = raop_rtp->metadata_len;
+       raop_rtp->metadata = NULL;
+       raop_rtp->metadata_len = 0;
+
+       /* Read the coverart */
+       coverart = raop_rtp->coverart;
+       coverart_len = raop_rtp->coverart_len;
+       raop_rtp->coverart = NULL;
+       raop_rtp->coverart_len = 0;
+       MUTEX_UNLOCK(raop_rtp->run_mutex);
+
+       /* Call set_volume callback if changed */
+       if (volume_changed) {
+               if (raop_rtp->callbacks.audio_set_volume) {
+                       raop_rtp->callbacks.audio_set_volume(raop_rtp->callbacks.cls, cb_data, *volume);
+               }
+       }
+
+       /* Handle flush if requested */
+       if (flush != NO_FLUSH) {
+               raop_buffer_flush(raop_rtp->buffer, flush);
+               if (raop_rtp->callbacks.audio_flush) {
+                       raop_rtp->callbacks.audio_flush(raop_rtp->callbacks.cls, cb_data);
+               }
+       }
+       if (metadata != NULL) {
+               if (raop_rtp->callbacks.audio_set_metadata) {
+                       raop_rtp->callbacks.audio_set_metadata(raop_rtp->callbacks.cls, cb_data, metadata, metadata_len);
+               }
+               free(metadata);
+               metadata = NULL;
+       }
+       if (coverart != NULL) {
+               if (raop_rtp->callbacks.audio_set_coverart) {
+                       raop_rtp->callbacks.audio_set_coverart(raop_rtp->callbacks.cls, cb_data, coverart, coverart_len);
+               }
+               free(coverart);
+               coverart = NULL;
+       }
        return 0;
 }
 
@@ -187,41 +332,20 @@ raop_rtp_thread_udp(void *arg)
        assert(raop_rtp);
 
        config = raop_buffer_get_config(raop_rtp->buffer);
-       raop_rtp->callbacks.audio_init(raop_rtp->callbacks.cls, &cb_data,
+       cb_data = raop_rtp->callbacks.audio_init(raop_rtp->callbacks.cls,
                                       config->bitDepth,
                                       config->numChannels,
                                       config->sampleRate);
 
        while(1) {
-               int volume_changed;
-               int flush;
-
                fd_set rfds;
                struct timeval tv;
                int nfds, ret;
 
-               MUTEX_LOCK(raop_rtp->run_mutex);
-               if (!raop_rtp->running) {
-                       MUTEX_UNLOCK(raop_rtp->run_mutex);
+               /* Check if we are still running and process callbacks */
+               if (raop_rtp_process_events(raop_rtp, cb_data, &volume)) {
                        break;
                }
-               /* Read the volume level */
-               volume_changed = (volume != raop_rtp->volume);
-               volume = raop_rtp->volume;
-
-               /* Read the flush value */
-               flush = raop_rtp->flush;
-               raop_rtp->flush = NO_FLUSH;
-               MUTEX_UNLOCK(raop_rtp->run_mutex);
-
-               /* Call set_volume callback if changed */
-               if (volume_changed) {
-                       raop_rtp->callbacks.audio_set_volume(raop_rtp->callbacks.cls, cb_data, volume);
-               }
-               if (flush != NO_FLUSH) {
-                       raop_buffer_flush(raop_rtp->buffer, flush);
-                       raop_rtp->callbacks.audio_flush(raop_rtp->callbacks.cls, cb_data);
-               }
 
                /* Set timeout value to 5ms */
                tv.tv_sec = 0;
@@ -253,14 +377,14 @@ raop_rtp_thread_udp(void *arg)
                        packetlen = recvfrom(raop_rtp->csock, (char *)packet, sizeof(packet), 0,
                                             (struct sockaddr *)&saddr, &saddrlen);
 
-                       /* FIXME: Get destination address here */
+                       /* Get the destination address here, because we need the sin6_scope_id */
                        memcpy(&raop_rtp->control_saddr, &saddr, saddrlen);
                        raop_rtp->control_saddr_len = saddrlen;
 
                        if (packetlen >= 12) {
                                char type = packet[1] & ~0x80;
 
-                               logger_log(raop_rtp->logger, LOGGER_DEBUG, "Got control packet of type 0x%02x\n", type);
+                               logger_log(raop_rtp->logger, LOGGER_DEBUG, "Got control packet of type 0x%02x", type);
                                if (type == 0x56) {
                                        /* Handle resent data packet */
                                        int ret = raop_buffer_queue(raop_rtp->buffer, packet+4, packetlen-4, 1);
@@ -268,7 +392,7 @@ raop_rtp_thread_udp(void *arg)
                                }
                        }
                } else if (FD_ISSET(raop_rtp->tsock, &rfds)) {
-                       logger_log(raop_rtp->logger, LOGGER_INFO, "Would have timing packet in queue\n");
+                       logger_log(raop_rtp->logger, LOGGER_INFO, "Would have timing packet in queue");
                } else if (FD_ISSET(raop_rtp->dsock, &rfds)) {
                        saddrlen = sizeof(saddr);
                        packetlen = recvfrom(raop_rtp->dsock, (char *)packet, sizeof(packet), 0,
@@ -295,7 +419,7 @@ raop_rtp_thread_udp(void *arg)
                        }
                }
        }
-       logger_log(raop_rtp->logger, LOGGER_INFO, "Exiting thread\n");
+       logger_log(raop_rtp->logger, LOGGER_INFO, "Exiting UDP RAOP thread");
        raop_rtp->callbacks.audio_destroy(raop_rtp->callbacks.cls, cb_data);
 
        return 0;
@@ -316,31 +440,20 @@ raop_rtp_thread_tcp(void *arg)
        assert(raop_rtp);
 
        config = raop_buffer_get_config(raop_rtp->buffer);
-       raop_rtp->callbacks.audio_init(raop_rtp->callbacks.cls, &cb_data,
+       cb_data = raop_rtp->callbacks.audio_init(raop_rtp->callbacks.cls,
                                       config->bitDepth,
                                       config->numChannels,
                                       config->sampleRate);
 
        while (1) {
-               int volume_changed;
-
                fd_set rfds;
                struct timeval tv;
                int nfds, ret;
 
-               MUTEX_LOCK(raop_rtp->run_mutex);
-               if (!raop_rtp->running) {
-                       MUTEX_UNLOCK(raop_rtp->run_mutex);
+               /* Check if we are still running and process callbacks */
+               if (raop_rtp_process_events(raop_rtp, cb_data, &volume)) {
                        break;
                }
-               volume_changed = (volume != raop_rtp->volume);
-               volume = raop_rtp->volume;
-               MUTEX_UNLOCK(raop_rtp->run_mutex);
-
-               /* Call set_volume callback if changed */
-               if (volume_changed) {
-                       raop_rtp->callbacks.audio_set_volume(raop_rtp->callbacks.cls, cb_data, volume);
-               }
 
                /* Set timeout value to 5ms */
                tv.tv_sec = 0;
@@ -361,19 +474,19 @@ raop_rtp_thread_tcp(void *arg)
                        continue;
                } else if (ret == -1) {
                        /* FIXME: Error happened */
-                       logger_log(raop_rtp->logger, LOGGER_INFO, "Error in select\n");
+                       logger_log(raop_rtp->logger, LOGGER_INFO, "Error in select");
                        break;
                }
                if (stream_fd == -1 && FD_ISSET(raop_rtp->dsock, &rfds)) {
                        struct sockaddr_storage saddr;
                        socklen_t saddrlen;
 
-                       logger_log(raop_rtp->logger, LOGGER_INFO, "Accepting client\n");
+                       logger_log(raop_rtp->logger, LOGGER_INFO, "Accepting client");
                        saddrlen = sizeof(saddr);
                        stream_fd = accept(raop_rtp->dsock, (struct sockaddr *)&saddr, &saddrlen);
                        if (stream_fd == -1) {
                                /* FIXME: Error happened */
-                               logger_log(raop_rtp->logger, LOGGER_INFO, "Error in accept %d %s\n", errno, strerror(errno));
+                               logger_log(raop_rtp->logger, LOGGER_INFO, "Error in accept %d %s", errno, strerror(errno));
                                break;
                        }
                }
@@ -386,11 +499,11 @@ raop_rtp_thread_tcp(void *arg)
                        ret = recv(stream_fd, (char *)(packet+packetlen), sizeof(packet)-packetlen, 0);
                        if (ret == 0) {
                                /* TCP socket closed */
-                               logger_log(raop_rtp->logger, LOGGER_INFO, "TCP socket closed\n");
+                               logger_log(raop_rtp->logger, LOGGER_INFO, "TCP socket closed");
                                break;
                        } else if (ret == -1) {
                                /* FIXME: Error happened */
-                               logger_log(raop_rtp->logger, LOGGER_INFO, "Error in recv\n");
+                               logger_log(raop_rtp->logger, LOGGER_INFO, "Error in recv");
                                break;
                        }
                        packetlen += ret;
@@ -406,7 +519,7 @@ raop_rtp_thread_tcp(void *arg)
                        rtplen = (packet[2] << 8) | packet[3];
                        if (rtplen > sizeof(packet)) {
                                /* FIXME: Too long packet */
-                               logger_log(raop_rtp->logger, LOGGER_INFO, "Error, packet too long %d\n", rtplen);
+                               logger_log(raop_rtp->logger, LOGGER_INFO, "Error, packet too long %d", rtplen);
                                break;
                        }
                        if (packetlen < 4+rtplen) {
@@ -433,7 +546,7 @@ raop_rtp_thread_tcp(void *arg)
                closesocket(stream_fd);
        }
 
-       logger_log(raop_rtp->logger, LOGGER_INFO, "Exiting thread\n");
+       logger_log(raop_rtp->logger, LOGGER_INFO, "Exiting TCP RAOP thread");
        raop_rtp->callbacks.audio_destroy(raop_rtp->callbacks.cls, cb_data);
 
        return 0;
@@ -443,6 +556,8 @@ void
 raop_rtp_start(raop_rtp_t *raop_rtp, int use_udp, unsigned short control_rport, unsigned short timing_rport,
                unsigned short *control_lport, unsigned short *timing_lport, unsigned short *data_lport)
 {
+       int use_ipv6 = 0;
+
        assert(raop_rtp);
 
        MUTEX_LOCK(raop_rtp->run_mutex);
@@ -454,8 +569,11 @@ raop_rtp_start(raop_rtp_t *raop_rtp, int use_udp, unsigned short control_rport,
        /* Initialize ports and sockets */
        raop_rtp->control_rport = control_rport;
        raop_rtp->timing_rport = timing_rport;
-       if (raop_rtp_init_sockets(raop_rtp, 1, use_udp) < 0) {
-               logger_log(raop_rtp->logger, LOGGER_INFO, "Initializing sockets failed\n");
+       if (raop_rtp->remote_saddr.ss_family == AF_INET6) {
+               use_ipv6 = 1;
+       }
+       if (raop_rtp_init_sockets(raop_rtp, use_ipv6, use_udp) < 0) {
+               logger_log(raop_rtp->logger, LOGGER_INFO, "Initializing sockets failed");
                MUTEX_UNLOCK(raop_rtp->run_mutex);
                return;
        }
@@ -491,6 +609,48 @@ raop_rtp_set_volume(raop_rtp_t *raop_rtp, float volume)
        MUTEX_UNLOCK(raop_rtp->run_mutex);
 }
 
+void
+raop_rtp_set_metadata(raop_rtp_t *raop_rtp, const char *data, int datalen)
+{
+       unsigned char *metadata;
+
+       assert(raop_rtp);
+
+       if (datalen <= 0) {
+               return;
+       }
+       metadata = malloc(datalen);
+       assert(metadata);
+       memcpy(metadata, data, datalen);
+
+       /* Set metadata in thread instead */
+       MUTEX_LOCK(raop_rtp->run_mutex);
+       raop_rtp->metadata = metadata;
+       raop_rtp->metadata_len = datalen;
+       MUTEX_UNLOCK(raop_rtp->run_mutex);
+}
+
+void
+raop_rtp_set_coverart(raop_rtp_t *raop_rtp, const char *data, int datalen)
+{
+       unsigned char *coverart;
+
+       assert(raop_rtp);
+
+       if (datalen <= 0) {
+               return;
+       }
+       coverart = malloc(datalen);
+       assert(coverart);
+       memcpy(coverart, data, datalen);
+
+       /* Set coverart in thread instead */
+       MUTEX_LOCK(raop_rtp->run_mutex);
+       raop_rtp->coverart = coverart;
+       raop_rtp->coverart_len = datalen;
+       MUTEX_UNLOCK(raop_rtp->run_mutex);
+}
+
 void
 raop_rtp_flush(raop_rtp_t *raop_rtp, int next_seq)
 {