diff options
author | Max Kellermann <max@duempel.org> | 2012-05-29 23:15:41 +0200 |
---|---|---|
committer | Max Kellermann <max@duempel.org> | 2012-05-29 23:15:41 +0200 |
commit | 457d98c8605c3406a2f83f8f099abe0048968efb (patch) | |
tree | 16c57c4ab55a219384eaed1a16fb902403a4994e | |
parent | 3c3f1b7ff27c13b817a7a94ef7605e06c93e4ff2 (diff) | |
download | mpd-457d98c8605c3406a2f83f8f099abe0048968efb.tar.gz mpd-457d98c8605c3406a2f83f8f099abe0048968efb.tar.xz mpd-457d98c8605c3406a2f83f8f099abe0048968efb.zip |
output/raop: delete the RAOP plugin
This plugin is horrible code, I mean it. Last year, I tried hard to
fix it, but I figured would take less time to do a full rewrite.
Given that I don't even have any device that supports RAOP, I can't do
that properly. After 16 months, nobody volunteered for fixing it.
Hereby, I delete it, because having no RAOP plugin is better than
having this mess. Sorry.
Diffstat (limited to '')
-rw-r--r-- | Makefile.am | 18 | ||||
-rw-r--r-- | NEWS | 1 | ||||
-rw-r--r-- | configure.ac | 18 | ||||
-rw-r--r-- | src/mixer/raop_mixer_plugin.c | 67 | ||||
-rw-r--r-- | src/mixer_list.h | 1 | ||||
-rw-r--r-- | src/ntp_server.c | 131 | ||||
-rw-r--r-- | src/ntp_server.h | 44 | ||||
-rw-r--r-- | src/output/raop_output_plugin.c | 1056 | ||||
-rw-r--r-- | src/output/raop_output_plugin.h | 37 | ||||
-rw-r--r-- | src/output_list.c | 4 | ||||
-rw-r--r-- | src/rtsp_client.c | 732 | ||||
-rw-r--r-- | src/rtsp_client.h | 124 | ||||
-rw-r--r-- | test/read_mixer.c | 19 | ||||
-rw-r--r-- | test/run_ntp_server.c | 71 |
14 files changed, 0 insertions, 2323 deletions
diff --git a/Makefile.am b/Makefile.am index c0cbbd99b..9b54b9990 100644 --- a/Makefile.am +++ b/Makefile.am @@ -841,15 +841,6 @@ if HAVE_OSX liboutput_plugins_a_SOURCES += src/output/osx_output_plugin.c endif -if ENABLE_RAOP_OUTPUT -liboutput_plugins_a_SOURCES += \ - src/ntp_server.c src/ntp_server.h \ - src/rtsp_client.c src/rtsp_client.h \ - src/output/raop_output_plugin.c -libmixer_plugins_a_SOURCES += src/mixer/raop_mixer_plugin.c -OUTPUT_LIBS += $(OPENSSL_LIBS) -endif - if HAVE_PULSE liboutput_plugins_a_SOURCES += \ src/output/pulse_output_plugin.c src/output/pulse_output_plugin.h @@ -992,7 +983,6 @@ noinst_PROGRAMS = \ test/dump_playlist \ test/run_decoder \ test/read_tags \ - test/run_ntp_server \ test/run_filter \ test/run_output \ test/run_convert \ @@ -1131,14 +1121,6 @@ test_dump_rva2_SOURCES = test/dump_rva2.c \ src/tag_rva2.c endif -test_run_ntp_server_LDADD = \ - $(GLIB_LIBS) -test_run_ntp_server_SOURCES = test/run_ntp_server.c \ - test/signals.c test/signals.h \ - src/io_thread.c src/io_thread.h \ - src/udp_server.c src/udp_server.h \ - src/ntp_server.c src/ntp_server.h - test_run_filter_LDADD = \ $(FILTER_LIBS) \ $(GLIB_LIBS) @@ -24,7 +24,6 @@ ver 0.17 (2011/??/??) - openal: improve buffer cancellation - osx: allow user to specify other audio devices - osx: implement 32 bit playback - - raop: new output plugin - shout: add possibility to set url - roar: new output plugin for RoarAudio - winmm: fail if wrong device specified instead of using default device diff --git a/configure.ac b/configure.ac index a3794924f..53b505abe 100644 --- a/configure.ac +++ b/configure.ac @@ -230,11 +230,6 @@ AC_ARG_ENABLE(httpd-output, [enables the HTTP server output]),, [enable_httpd_output=auto]) -AC_ARG_ENABLE(raop-output, - AS_HELP_STRING([--enable-raop-output], - [enables the RAOP output]),, - [enable_raop_output=auto]) - AC_ARG_ENABLE(id3, AS_HELP_STRING([--enable-id3], [enable id3 support]),, @@ -1428,17 +1423,6 @@ fi AM_CONDITIONAL(ENABLE_SOLARIS_OUTPUT, test x$enable_solaris_output = xyes) -dnl --------------------------------- RAOP ------------------------------------ - -MPD_AUTO_PKG(raop_output, OPENSSL, [openssl], - [RAOP output], [OpenSSL not found]) - -if test x$enable_raop_output = xyes; then - AC_DEFINE(ENABLE_RAOP_OUTPUT, 1, [Define for compiling RAOP support]) -fi - -AM_CONDITIONAL(ENABLE_RAOP_OUTPUT, test x$enable_raop_output = xyes) - dnl --------------------------------- WinMM --------------------------------- case "$host_os" in @@ -1468,7 +1452,6 @@ if test x$enable_openal = xno && test x$enable_oss = xno && test x$enable_osx = xno && - test x$enable_raop_output = xno && test x$enable_pipe_output = xno && test x$enable_pulse = xno && test x$enable_recorder_output = xno && @@ -1601,7 +1584,6 @@ results(osx, [OS X]) results(pipe_output, [Pipeline]) printf '\n\t' results(pulse, [PulseAudio]) -results(raop_output, [RAOP]) results(roar,[ROAR]) results(shout, [SHOUTcast]) results(solaris_output, [Solaris]) diff --git a/src/mixer/raop_mixer_plugin.c b/src/mixer/raop_mixer_plugin.c deleted file mode 100644 index b05671212..000000000 --- a/src/mixer/raop_mixer_plugin.c +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2003-2009 The Music Player Daemon Project - * http://www.musicpd.org - * - * 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., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "../output/raop_output_plugin.h" -#include "output_plugin.h" -#include "mixer_api.h" - -struct raop_mixer_plugin { - struct mixer base; - struct raop_data *rd; -}; - -static struct mixer * -raop_mixer_init(void *ao, G_GNUC_UNUSED const struct config_param *param, - G_GNUC_UNUSED GError **error_r) -{ - struct raop_mixer_plugin *rm = g_new(struct raop_mixer_plugin, 1); - rm->rd = (struct raop_data *) ao; - mixer_init(&rm->base, &raop_mixer_plugin); - - return &rm->base; -} - -static void -raop_mixer_finish(struct mixer *data) -{ - struct raop_mixer_plugin *rm = (struct raop_mixer_plugin *) data; - - g_free(rm); -} - -static int -raop_mixer_get_volume(struct mixer *mixer, G_GNUC_UNUSED GError **error_r) -{ - struct raop_mixer_plugin *rm = (struct raop_mixer_plugin *)mixer; - return raop_get_volume(rm->rd); -} - -static bool -raop_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r) -{ - struct raop_mixer_plugin *rm = (struct raop_mixer_plugin *)mixer; - return raop_set_volume(rm->rd, volume, error_r); -} - -const struct mixer_plugin raop_mixer_plugin = { - .init = raop_mixer_init, - .finish = raop_mixer_finish, - .get_volume = raop_mixer_get_volume, - .set_volume = raop_mixer_set_volume, -}; diff --git a/src/mixer_list.h b/src/mixer_list.h index 95ded5c23..078358ec3 100644 --- a/src/mixer_list.h +++ b/src/mixer_list.h @@ -30,7 +30,6 @@ extern const struct mixer_plugin alsa_mixer_plugin; extern const struct mixer_plugin oss_mixer_plugin; extern const struct mixer_plugin roar_mixer_plugin; extern const struct mixer_plugin pulse_mixer_plugin; -extern const struct mixer_plugin raop_mixer_plugin; extern const struct mixer_plugin winmm_mixer_plugin; #endif diff --git a/src/ntp_server.c b/src/ntp_server.c deleted file mode 100644 index f31a2d845..000000000 --- a/src/ntp_server.c +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * 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., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "ntp_server.h" -#include "udp_server.h" - -#include <glib.h> -#include <assert.h> -#include <stddef.h> -#include <stdint.h> -#include <string.h> -#include <unistd.h> -#include <sys/time.h> - -#ifdef WIN32 -#include <ws2tcpip.h> -#include <winsock.h> -#else -#include <sys/socket.h> -#endif - -/* - * Calculate the current NTP time, store it in the buffer. - */ -static void -fill_int(unsigned char *buffer, uint32_t value) -{ - uint32_t be = GINT32_TO_BE(value); - memcpy(buffer, &be, sizeof(be)); -} - -/* - * Store time in the NTP format in the buffer - */ -static void -fill_time_buffer_with_time(unsigned char *buffer, struct timeval *tout) -{ - unsigned long secs_to_baseline = 964697997; - double fraction; - unsigned long long_fraction; - unsigned long secs; - - fraction = ((double) tout->tv_usec) / 1000000.0; - long_fraction = (unsigned long) (fraction * 256.0 * 256.0 * 256.0 * 256.0); - secs = secs_to_baseline + tout->tv_sec; - fill_int(buffer, secs); - fill_int(buffer + 4, long_fraction); -} - -/* - * Calculate the current NTP time, store it in the buffer. - */ -static void -fill_time_buffer(unsigned char *buffer) -{ - struct timeval current_time; - - gettimeofday(¤t_time,NULL); - fill_time_buffer_with_time(buffer, ¤t_time); -} - -static void -ntp_server_datagram(int fd, const void *data, size_t num_bytes, - const struct sockaddr *source_address, - size_t source_address_length, G_GNUC_UNUSED void *ctx) -{ - unsigned char buf[32]; - int iter; - - if (num_bytes > sizeof(buf)) - num_bytes = sizeof(buf); - memcpy(buf, data, num_bytes); - - fill_time_buffer(buf + 16); - // set to response - buf[1] = 0xd3; - // copy request - for (iter = 0; iter < 8; iter++) { - buf[8 + iter] = buf[24 + iter]; - } - fill_time_buffer(buf + 24); - - sendto(fd, (void *)buf, num_bytes, 0, - source_address, source_address_length); -} - -static const struct udp_server_handler ntp_server_handler = { - .datagram = ntp_server_datagram, -}; - -void -ntp_server_init(struct ntp_server *ntp) -{ - ntp->port = 6002; - ntp->udp = NULL; -} - -bool -ntp_server_open(struct ntp_server *ntp, GError **error_r) -{ - assert(ntp->udp == NULL); - - ntp->udp = udp_server_new(ntp->port, &ntp_server_handler, ntp, - error_r); - return ntp->udp != NULL; -} - -void -ntp_server_close(struct ntp_server *ntp) -{ - if (ntp->udp != NULL) { - udp_server_free(ntp->udp); - ntp->udp = NULL; - } -} diff --git a/src/ntp_server.h b/src/ntp_server.h deleted file mode 100644 index fe6f8083b..000000000 --- a/src/ntp_server.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * 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., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_NTP_SERVER_H -#define MPD_NTP_SERVER_H - -#include <glib.h> - -#include <stdbool.h> - -struct timeval; - -struct ntp_server { - unsigned short port; - - struct udp_server *udp; -}; - -void -ntp_server_init(struct ntp_server *ntp); - -bool -ntp_server_open(struct ntp_server *ntp, GError **error_r); - -void -ntp_server_close(struct ntp_server *ntp); - -#endif diff --git a/src/output/raop_output_plugin.c b/src/output/raop_output_plugin.c deleted file mode 100644 index 6177b9b7d..000000000 --- a/src/output/raop_output_plugin.c +++ /dev/null @@ -1,1056 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * 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., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "raop_output_plugin.h" -#include "output_api.h" -#include "mixer_list.h" -#include "fd_util.h" -#include "ntp_server.h" -#include "rtsp_client.h" -#include "glib_compat.h" - -#include <glib.h> -#include <unistd.h> -#include <sys/time.h> -#include <openssl/aes.h> -#include <openssl/err.h> -#include <openssl/rand.h> -#include <openssl/rsa.h> -#include <openssl/engine.h> - -#ifndef WIN32 -#include <arpa/inet.h> -#include <sys/socket.h> -#include <netdb.h> -#endif - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "raop" - -struct play_state { - bool playing; - unsigned short seq_num; - unsigned int rtptime; - unsigned int sync_src; - unsigned int start_rtptime; - struct timeval start_time; - struct timeval last_send; -}; - -/*********************************************************************/ - -enum pause_state { - NO_PAUSE = 0, - OP_PAUSE, - NODATA_PAUSE, -}; - -#define MINIMUM_SAMPLE_SIZE 32 - -#define RAOP_FD_READ (1<<0) -#define RAOP_FD_WRITE (1<<1) - -/*********************************************************************/ - -struct encrypt_data { - AES_KEY ctx; - unsigned char iv[16]; // initialization vector for aes-cbc - unsigned char nv[16]; // next vector for aes-cbc - unsigned char key[16]; // key for aes-cbc -}; - -/*********************************************************************/ - -struct raop_data { - struct audio_output base; - - struct rtspcl_data *rtspcl; - const char *addr; // target host address - short rtsp_port; - struct sockaddr_in ctrl_addr; - struct sockaddr_in data_addr; - - bool is_master; - struct raop_data *next; - - unsigned volume; - - GMutex *control_mutex; - - bool started; - bool paused; -}; - -/*********************************************************************/ - -struct control_data { - unsigned short port; - int fd; -}; - -/*********************************************************************/ - -#define NUMSAMPLES 352 -#define RAOP_BUFFER_SIZE NUMSAMPLES * 4 -#define RAOP_HEADER_SIZE 12 -#define ALAC_MAX_HEADER_SIZE 8 -#define RAOP_MAX_PACKET_SIZE RAOP_BUFFER_SIZE + RAOP_HEADER_SIZE + ALAC_MAX_HEADER_SIZE - -// session -struct raop_session_data { - struct raop_data *raop_list; - struct ntp_server ntp; - struct control_data ctrl; - struct encrypt_data encrypt; - struct play_state play_state; - - int data_fd; - - unsigned char buffer[RAOP_BUFFER_SIZE]; - size_t bufferSize; - - unsigned char data[RAOP_MAX_PACKET_SIZE]; - int wblk_wsize; - int wblk_remsize; - - GMutex *data_mutex; - GMutex *list_mutex; -}; - -/*********************************************************************/ - -static struct raop_session_data *raop_session = NULL; - -/** - * The quark used for GError.domain. - */ -static inline GQuark -raop_output_quark(void) -{ - return g_quark_from_static_string("raop_output"); -} - -static void -raop_session_free(struct raop_session_data *session) -{ - assert(session != NULL); - assert(session->raop_list == NULL); - - ntp_server_close(&session->ntp); - - if (session->data_mutex != NULL) - g_mutex_free(session->data_mutex); - - if (session->list_mutex != NULL) - g_mutex_free(session->list_mutex); - - if (raop_session->data_fd >= 0) - close_socket(raop_session->data_fd); - - if (raop_session->ctrl.fd >= 0) - close_socket(raop_session->ctrl.fd); - - g_free(session); -} - -static struct raop_session_data * -raop_session_new(GError **error_r) -{ - struct raop_session_data *session = g_new(struct raop_session_data, 1); - session->raop_list = NULL; - - session->data_mutex = g_mutex_new(); - session->list_mutex = g_mutex_new(); - - ntp_server_init(&session->ntp); - session->ctrl.port = 6001; - session->ctrl.fd = -1; - session->play_state.playing = false; - session->play_state.seq_num = (short) g_random_int(); - session->play_state.rtptime = g_random_int(); - session->play_state.sync_src = g_random_int(); - session->play_state.last_send.tv_sec = 0; - session->play_state.last_send.tv_usec = 0; - session->data_fd = -1; - - if (!RAND_bytes(session->encrypt.iv, sizeof(session->encrypt.iv)) || - !RAND_bytes(session->encrypt.key, sizeof(session->encrypt.key))) { - raop_session_free(session); - g_set_error(error_r, raop_output_quark(), 0, - "RAND_bytes error code=%ld", ERR_get_error()); - return NULL; - } - memcpy(session->encrypt.nv, session->encrypt.iv, sizeof(session->encrypt.nv)); - for (unsigned i = 0; i < 16; i++) { - printf("0x%x ", session->encrypt.key[i]); - } - printf("\n"); - AES_set_encrypt_key(session->encrypt.key, 128, &session->encrypt.ctx); - - memset(session->buffer, 0, RAOP_BUFFER_SIZE); - session->bufferSize = 0; - - return session; -} - -static struct raop_data * -new_raop_data(const struct config_param *param, GError **error_r) -{ - struct raop_data *ret = g_new(struct raop_data, 1); - if (!ao_base_init(&ret->base, &raop_output_plugin, param, error_r)) { - g_free(ret); - return NULL; - } - - ret->control_mutex = g_mutex_new(); - - ret->next = NULL; - ret->is_master = 0; - ret->started = 0; - ret->paused = 0; - - if (raop_session == NULL && - (raop_session = raop_session_new(error_r)) == NULL) { - g_mutex_free(ret->control_mutex); - ao_base_finish(&ret->base); - g_free(ret); - return NULL; - } - - return ret; -} - -/* - * remove one character from a string - * return the number of deleted characters - */ -static int -remove_char_from_string(char *str, char c) -{ - char *src, *dst; - - /* skip all characters that don't need to be copied */ - src = strchr(str, c); - if (!src) - return 0; - - for (dst = src; *src; src++) - if (*src != c) - *(dst++) = *src; - - *dst = '\0'; - - return src - dst; -} - -/* bind an opened socket to specified hostname and port. - * if hostname=NULL, use INADDR_ANY. - * if *port=0, use dynamically assigned port - */ -static int bind_host(int sd, char *hostname, unsigned long ulAddr, - unsigned short *port, GError **error_r) -{ - struct sockaddr_in my_addr; - socklen_t nlen = sizeof(struct sockaddr); - struct hostent *h; - - memset(&my_addr, 0, sizeof(my_addr)); - /* use specified hostname */ - if (hostname) { - /* get server IP address (no check if input is IP address or DNS name) */ - h = gethostbyname(hostname); - if (h == NULL) { - if (strstr(hostname, "255.255.255.255") == hostname) { - my_addr.sin_addr.s_addr=-1; - } else { - if ((my_addr.sin_addr.s_addr = inet_addr(hostname)) == 0xFFFFFFFF) { - g_set_error(error_r, raop_output_quark(), 0, - "failed to resolve host '%s'", - hostname); - return -1; - } - } - my_addr.sin_family = AF_INET; - } else { - my_addr.sin_family = h->h_addrtype; - memcpy((char *) &my_addr.sin_addr.s_addr, - h->h_addr_list[0], h->h_length); - } - } else { - // if hostname=NULL, use INADDR_ANY - if (ulAddr) - my_addr.sin_addr.s_addr = ulAddr; - else - my_addr.sin_addr.s_addr = htonl(INADDR_ANY); - my_addr.sin_family = AF_INET; - } - - /* bind a specified port */ - my_addr.sin_port = htons(*port); - - if (bind(sd, (struct sockaddr *) &my_addr, sizeof(my_addr)) < 0) { - g_set_error(error_r, raop_output_quark(), errno, - "failed to bind socket: %s", - g_strerror(errno)); - return -1; - } - - if (*port == 0) { - getsockname(sd, (struct sockaddr *) &my_addr, &nlen); - *port = ntohs(my_addr.sin_port); - } - - return 0; -} - -/* - * open udp port - */ -static int -open_udp_socket(char *hostname, unsigned short *port, - GError **error_r) -{ - int sd; - const int size = 30000; - - /* socket creation */ - sd = socket(PF_INET, SOCK_DGRAM, 0); - if (sd < 0) { - g_set_error(error_r, raop_output_quark(), errno, - "failed to create UDP socket: %s", - g_strerror(errno)); - return -1; - } - if (setsockopt(sd, SOL_SOCKET, SO_SNDBUF, (const char *) &size, sizeof(size)) < 0) { - g_set_error(error_r, raop_output_quark(), errno, - "failed to set UDP buffer size: %s", - g_strerror(errno)); - return -1; - } - if (bind_host(sd, hostname, 0, port, error_r)) { - close_socket(sd); - return -1; - } - - return sd; -} - -static bool -get_sockaddr_by_host(const char *host, short destport, - struct sockaddr_in *addr, - GError **error_r) -{ - struct hostent *h; - - h = gethostbyname(host); - if (h) { - addr->sin_family = h->h_addrtype; - memcpy((char *) &addr->sin_addr.s_addr, h->h_addr_list[0], h->h_length); - } else { - addr->sin_family = AF_INET; - if ((addr->sin_addr.s_addr=inet_addr(host))==0xFFFFFFFF) { - g_set_error(error_r, rtsp_client_quark(), 0, - "failed to resolve host '%s'", host); - return false; - } - } - addr->sin_port = htons(destport); - return true; -} - -/* - * Calculate the current NTP time, store it in the buffer. - */ -static void -fill_int(unsigned char *buffer, uint32_t value) -{ - uint32_t be = GINT32_TO_BE(value); - memcpy(buffer, &be, sizeof(be)); -} - -/* - * Store time in the NTP format in the buffer - */ -static void -fill_time_buffer_with_time(unsigned char *buffer, struct timeval *tout) -{ - unsigned long secs_to_baseline = 964697997; - double fraction; - unsigned long long_fraction; - unsigned long secs; - - fraction = ((double) tout->tv_usec) / 1000000.0; - long_fraction = (unsigned long) (fraction * 256.0 * 256.0 * 256.0 * 256.0); - secs = secs_to_baseline + tout->tv_sec; - fill_int(buffer, secs); - fill_int(buffer + 4, long_fraction); -} - -static void -get_time_for_rtp(struct play_state *state, struct timeval *tout) -{ - unsigned long rtp_diff = state->rtptime - state->start_rtptime; - unsigned long add_secs = rtp_diff / 44100; - unsigned long add_usecs = (((rtp_diff % 44100) * 10000) / 441) % 1000000; - tout->tv_sec = state->start_time.tv_sec + add_secs; - tout->tv_usec = state->start_time.tv_usec + add_usecs; - if (tout->tv_usec >= 1000000) { - tout->tv_sec++; - tout->tv_usec = tout->tv_usec % 1000000; - } -} - -/* - * Send a control command - */ -static bool -send_control_command(struct control_data *ctrl, struct raop_data *rd, - struct play_state *state, - GError **error_r) -{ - unsigned char buf[20]; - int diff; - int num_bytes; - struct timeval ctrl_time; - - diff = 88200; - if (rd->started) { - buf[0] = 0x80; - diff += NUMSAMPLES; - } else { - buf[0] = 0x90; - state->playing = true; - state->start_rtptime = state->rtptime; - } - buf[1] = 0xd4; - buf[2] = 0x00; - buf[3] = 0x07; - fill_int(buf + 4, state->rtptime - diff); - get_time_for_rtp(state, &ctrl_time); - fill_time_buffer_with_time(buf + 8, &ctrl_time); - fill_int(buf + 16, state->rtptime); - - num_bytes = sendto(ctrl->fd, (const void *)buf, sizeof(buf), 0, - (struct sockaddr *)&rd->ctrl_addr, - sizeof(rd->ctrl_addr)); - if (num_bytes < 0) { - g_set_error(error_r, raop_output_quark(), errno, - "Unable to send control command: %s", - g_strerror(errno)); - return false; - } - - return true; -} - -static int rsa_encrypt(const unsigned char *text, int len, unsigned char *res) -{ - RSA *rsa; - gsize usize; - unsigned char *modulus; - unsigned char *exponent; - int size; - - char n[] = - "59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUtwC" - "5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDR" - "KSKv6kDqnw4UwPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuB" - "OitnZ/bDzPHrTOZz0Dew0uowxf/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJ" - "Q+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/UAaHqn9JdsBWLUEpVviYnh" - "imNVvYFZeCXg/IdTQ+x4IRdiXNv5hEew=="; - char e[] = "AQAB"; - - rsa = RSA_new(); - - modulus = g_base64_decode(n, &usize); - rsa->n = BN_bin2bn(modulus, usize, NULL); - exponent = g_base64_decode(e, &usize); - rsa->e = BN_bin2bn(exponent, usize, NULL); - g_free(modulus); - g_free(exponent); - size = RSA_public_encrypt(len, text, res, rsa, RSA_PKCS1_OAEP_PADDING); - - RSA_free(rsa); - return size; -} - -static int -raop_encrypt(struct encrypt_data *encryp, unsigned char *data, int size) -{ - // any bytes that fall beyond the last 16 byte page should be sent - // in the clear - int alt_size = size - (size % 16); - - memcpy(encryp->nv, encryp->iv, 16); - - AES_cbc_encrypt(data, data, alt_size, &encryp->ctx, encryp->nv, 1); - - return size; -} - -/* write bits filed data, *bpos=0 for msb, *bpos=7 for lsb - d=data, blen=length of bits field -*/ -static inline void -bits_write(unsigned char **p, unsigned char d, int blen, int *bpos) -{ - int lb, rb, bd; - lb =7 - *bpos; - rb = lb - blen + 1; - if (rb >= 0) { - bd = d << rb; - if (*bpos) - **p |= bd; - else - **p = bd; - *bpos += blen; - } else { - bd = d >> -rb; - **p |= bd; - *p += 1; - **p = d << (8 + rb); - *bpos = -rb; - } -} - -static bool -wrap_pcm(unsigned char *buffer, int bsize, int *size, unsigned char *inData, int inSize) -{ - unsigned char one[4]; - int count = 0; - int bpos = 0; - unsigned char *bp = buffer; - int i, nodata = 0; - bits_write(&bp, 1, 3, &bpos); // channel=1, stereo - bits_write(&bp, 0, 4, &bpos); // unknown - bits_write(&bp, 0, 8, &bpos); // unknown - bits_write(&bp, 0, 4, &bpos); // unknown - if (bsize != 4096 && false) - bits_write(&bp, 1, 1, &bpos); // hassize - else - bits_write(&bp, 0, 1, &bpos); // hassize - bits_write(&bp, 0, 2, &bpos); // unused - bits_write(&bp, 1, 1, &bpos); // is-not-compressed - if (bsize != 4096 && false) { - // size of data, integer, big endian - bits_write(&bp, (bsize >> 24) & 0xff, 8, &bpos); - bits_write(&bp, (bsize >> 16) & 0xff, 8, &bpos); - bits_write(&bp, (bsize >> 8) & 0xff, 8, &bpos); - bits_write(&bp, bsize&0xff, 8, &bpos); - } - while (1) { - if (inSize <= count * 4) nodata = 1; - if (nodata) break; - one[0] = inData[count * 4]; - one[1] = inData[count * 4 + 1]; - one[2] = inData[count * 4 + 2]; - one[3] = inData[count * 4 + 3]; - -#if BYTE_ORDER == BIG_ENDIAN - bits_write(&bp, one[0], 8, &bpos); - bits_write(&bp, one[1], 8, &bpos); - bits_write(&bp, one[2], 8, &bpos); - bits_write(&bp, one[3], 8, &bpos); -#else - bits_write(&bp, one[1], 8, &bpos); - bits_write(&bp, one[0], 8, &bpos); - bits_write(&bp, one[3], 8, &bpos); - bits_write(&bp, one[2], 8, &bpos); -#endif - - if (++count == bsize) break; - } - if (!count) return false; // when no data at all, it should stop playing - /* when readable size is less than bsize, fill 0 at the bottom */ - for(i = 0; i < (bsize - count) * 4; i++) { - bits_write(&bp, 0, 8, &bpos); - } - *size = (int)(bp - buffer); - if (bpos) *size += 1; - return true; -} - -static bool -raopcl_connect(struct raop_data *rd, GError **error_r) -{ - unsigned char buf[4 + 8 + 16]; - char sid[16]; - char sci[24]; - char act_r[17]; - char *sac=NULL, *key = NULL, *iv = NULL; - char sdp[1024]; - int rval = false; - unsigned char rsakey[512]; - struct timeval current_time; - unsigned int sessionNum; - int i; - - - gettimeofday(¤t_time,NULL); - sessionNum = current_time.tv_sec + 2082844804; - - RAND_bytes(buf, sizeof(buf)); - sprintf(act_r, "%u", (unsigned int) g_random_int()); - sprintf(sid, "%u", sessionNum); - sprintf(sci, "%08x%08x", *((int *)(buf + 4)), *((int *)(buf + 8))); - sac = g_base64_encode(buf + 12, 16); - rd->rtspcl = rtspcl_open(); - rtspcl_set_useragent(rd->rtspcl, "iTunes/8.1.1 (Macintosh; U; PPC Mac OS X 10.4)"); - rtspcl_add_exthds(rd->rtspcl, "Client-Instance", sci); - rtspcl_add_exthds(rd->rtspcl, "DACP-ID", sci); - rtspcl_add_exthds(rd->rtspcl, "Active-Remote", act_r); - if (!rtspcl_connect(rd->rtspcl, rd->addr, rd->rtsp_port, sid, error_r)) - goto erexit; - - i = rsa_encrypt(raop_session->encrypt.key, 16, rsakey); - key = g_base64_encode(rsakey, i); - remove_char_from_string(key, '='); - iv = g_base64_encode(raop_session->encrypt.iv, 16); - remove_char_from_string(iv, '='); - sprintf(sdp, - "v=0\r\n" - "o=iTunes %s 0 IN IP4 %s\r\n" - "s=iTunes\r\n" - "c=IN IP4 %s\r\n" - "t=0 0\r\n" - "m=audio 0 RTP/AVP 96\r\n" - "a=rtpmap:96 AppleLossless\r\n" - "a=fmtp:96 %d 0 16 40 10 14 2 255 0 0 44100\r\n" - "a=rsaaeskey:%s\r\n" - "a=aesiv:%s\r\n", - sid, rtspcl_local_ip(rd->rtspcl), rd->addr, NUMSAMPLES, key, iv); - remove_char_from_string(sac, '='); - // rtspcl_add_exthds(rd->rtspcl, "Apple-Challenge", sac); - if (!rtspcl_announce_sdp(rd->rtspcl, sdp, error_r)) - goto erexit; - // if (!rtspcl_mark_del_exthds(rd->rtspcl, "Apple-Challenge")) goto erexit; - if (!rtspcl_setup(rd->rtspcl, NULL, - raop_session->ctrl.port, raop_session->ntp.port, - error_r)) - goto erexit; - - if (!get_sockaddr_by_host(rd->addr, rd->rtspcl->control_port, - &rd->ctrl_addr, error_r)) - goto erexit; - - if (!get_sockaddr_by_host(rd->addr, rd->rtspcl->server_port, - &rd->data_addr, error_r)) - goto erexit; - - if (!rtspcl_record(rd->rtspcl, - raop_session->play_state.seq_num, - raop_session->play_state.rtptime, - error_r)) - goto erexit; - - rval = true; - - erexit: - g_free(sac); - g_free(key); - g_free(iv); - return rval; -} - -static int -difference (struct timeval *t1, struct timeval *t2) -{ - int ret = 150000000; - if (t1->tv_sec - t2->tv_sec < 150) { - ret = (t1->tv_sec - t2->tv_sec) * 1000000; - ret += t1->tv_usec - t2->tv_usec; - } - return ret; -} - -/* - * With airtunes version 2, we don't get responses back when we send audio - * data. The only requests we get from the airtunes device are timing - * requests. - */ -static bool -send_audio_data(int fd, GError **error_r) -{ - int i = 0; - struct timeval current_time, rtp_time; - struct raop_data *rd = raop_session->raop_list; - - get_time_for_rtp(&raop_session->play_state, &rtp_time); - gettimeofday(¤t_time, NULL); - int diff = difference(&rtp_time, ¤t_time); - if (diff > 0) - g_usleep(diff); - - gettimeofday(&raop_session->play_state.last_send, NULL); - while (rd) { - if (rd->started) { - raop_session->data[1] = 0x60; - } else { - rd->started = true; - raop_session->data[1] = 0xe0; - } - i = sendto(fd, (const void *)(raop_session->data + raop_session->wblk_wsize), - raop_session->wblk_remsize, 0, (struct sockaddr *) &rd->data_addr, - sizeof(rd->data_addr)); - if (i < 0) { - g_set_error(error_r, raop_output_quark(), errno, - "write error: %s", - g_strerror(errno)); - return false; - } - if (i == 0) { - g_set_error_literal(error_r, raop_output_quark(), 0, - "disconnected on the other end"); - return false; - } - rd = rd->next; - } - raop_session->wblk_wsize += i; - raop_session->wblk_remsize -= i; - - return true; -} - -static struct audio_output * -raop_output_init(const struct config_param *param, GError **error_r) -{ - const char *host = config_get_block_string(param, "host", NULL); - if (host == NULL) { - g_set_error_literal(error_r, raop_output_quark(), 0, - "missing option 'host'"); - return NULL; - } - - struct raop_data *rd; - - rd = new_raop_data(param, error_r); - if (rd == NULL) - return NULL; - - rd->addr = host; - rd->rtsp_port = config_get_block_unsigned(param, "port", 5000); - rd->volume = config_get_block_unsigned(param, "volume", 75); - return &rd->base; -} - -static bool -raop_set_volume_local(struct raop_data *rd, int volume, GError **error_r) -{ - char vol_str[128]; - sprintf(vol_str, "volume: %d.000000\r\n", volume); - return rtspcl_set_parameter(rd->rtspcl, vol_str, error_r); -} - - -static void -raop_output_finish(struct audio_output *ao) -{ - struct raop_data *rd = (struct raop_data *)ao; - - if (rd->rtspcl) - rtspcl_close(rd->rtspcl); - - g_mutex_free(rd->control_mutex); - ao_base_finish(&rd->base); - g_free(rd); - - if (raop_session->raop_list == NULL) { - raop_session_free(raop_session); - raop_session = NULL; - } -} - -#define RAOP_VOLUME_MIN -30 -#define RAOP_VOLUME_MAX 0 - -int -raop_get_volume(struct raop_data *rd) -{ - return rd->volume; -} - -bool -raop_set_volume(struct raop_data *rd, unsigned volume, GError **error_r) -{ - int raop_volume; - bool rval; - - //set parameter volume - if (volume == 0) { - raop_volume = -144; - } else { - raop_volume = RAOP_VOLUME_MIN + - (RAOP_VOLUME_MAX - RAOP_VOLUME_MIN) * volume / 100; - } - g_mutex_lock(rd->control_mutex); - rval = raop_set_volume_local(rd, raop_volume, error_r); - if (rval) rd->volume = volume; - g_mutex_unlock(rd->control_mutex); - - return rval; -} - -static void -raop_output_cancel(struct audio_output *ao) -{ - //flush - struct key_data kd; - struct raop_data *rd = (struct raop_data *)ao; - int flush_diff = 1; - - rd->started = 0; - if (rd->is_master) { - raop_session->play_state.playing = false; - } - if (rd->paused) { - return; - } - - g_mutex_lock(rd->control_mutex); - static char rtp_key[] = "RTP-Info"; - kd.key = rtp_key; - char buf[128]; - sprintf(buf, "seq=%d; rtptime=%d", raop_session->play_state.seq_num + flush_diff, raop_session->play_state.rtptime + NUMSAMPLES * flush_diff); - kd.data = buf; - kd.next = NULL; - exec_request(rd->rtspcl, "FLUSH", NULL, NULL, 1, - &kd, NULL, NULL); - g_mutex_unlock(rd->control_mutex); -} - -static bool -raop_output_pause(struct audio_output *ao) -{ - struct raop_data *rd = (struct raop_data *)ao; - - rd->paused = true; - return true; -} - -/** - * Remove the output from the session's list. Caller must not lock - * the list_mutex. - */ -static void -raop_output_remove(struct raop_data *rd) -{ - struct raop_data *iter = raop_session->raop_list; - struct raop_data *prev = NULL; - - g_mutex_lock(raop_session->list_mutex); - while (iter) { - if (iter == rd) { - if (prev != NULL) { - prev->next = rd->next; - } else { - raop_session->raop_list = rd->next; - } - if (rd->is_master && raop_session->raop_list != NULL) { - raop_session->raop_list->is_master = true; - } - rd->next = NULL; - rd->is_master = false; - break; - } - prev = iter; - iter = iter->next; - } - g_mutex_unlock(raop_session->list_mutex); - - if (raop_session->raop_list == NULL) { - ntp_server_close(&raop_session->ntp); - close(raop_session->ctrl.fd); - raop_session->ctrl.fd = -1; - } -} - -static void -raop_output_close(struct audio_output *ao) -{ - //teardown - struct raop_data *rd = (struct raop_data *)ao; - - raop_output_remove(rd); - - g_mutex_lock(rd->control_mutex); - exec_request(rd->rtspcl, "TEARDOWN", NULL, NULL, 0, - NULL, NULL, NULL); - g_mutex_unlock(rd->control_mutex); - - rd->started = 0; -} - - -static bool -raop_output_open(struct audio_output *ao, struct audio_format *audio_format, GError **error_r) -{ - //setup, etc. - struct raop_data *rd = (struct raop_data *)ao; - - g_mutex_lock(raop_session->list_mutex); - if (raop_session->raop_list == NULL) { - // first raop, need to initialize session data - unsigned short myport = 0; - raop_session->raop_list = rd; - rd->is_master = true; - - raop_session->data_fd = open_udp_socket(NULL, &myport, - error_r); - if (raop_session->data_fd < 0) - return false; - - if (!ntp_server_open(&raop_session->ntp, error_r)) - return false; - - raop_session->ctrl.fd = - open_udp_socket(NULL, &raop_session->ctrl.port, - error_r); - if (raop_session->ctrl.fd < 0) { - ntp_server_close(&raop_session->ntp); - raop_session->ctrl.fd = -1; - g_mutex_unlock(raop_session->list_mutex); - return false; - } - } - g_mutex_unlock(raop_session->list_mutex); - - audio_format->format = SAMPLE_FORMAT_S16; - if (!raopcl_connect(rd, error_r)) { - raop_output_remove(rd); - return false; - } - - if (!raop_set_volume(rd, rd->volume, error_r)) { - raop_output_remove(rd); - return false; - } - - g_mutex_lock(raop_session->list_mutex); - if (!rd->is_master) { - rd->next = raop_session->raop_list; - raop_session->raop_list = rd; - } - g_mutex_unlock(raop_session->list_mutex); - return true; -} - -static size_t -raop_output_play(struct audio_output *ao, const void *chunk, size_t size, - GError **error_r) -{ - //raopcl_send_sample - struct raop_data *rd = (struct raop_data *)ao; - size_t rval = 0, orig_size = size; - - rd->paused = false; - if (!rd->is_master) { - // only process data for the master raop - return size; - } - - g_mutex_lock(raop_session->data_mutex); - - if (raop_session->play_state.rtptime <= NUMSAMPLES) { - // looped over, need new reference point to calculate correct times - raop_session->play_state.playing = false; - } - - while (raop_session->bufferSize + size >= RAOP_BUFFER_SIZE) { - // ntp header - unsigned char header[] = { - 0x80, 0x60, 0x00, 0x00, - // rtptime - 0x00, 0x00, 0x00, 0x00, - // device - 0x7e, 0xad, 0xd2, 0xd3, - }; - - - int count = 0; - int copyBytes = RAOP_BUFFER_SIZE - raop_session->bufferSize; - - if (!raop_session->play_state.playing || - raop_session->play_state.seq_num % (44100 / NUMSAMPLES + 1) == 0) { - struct raop_data *iter; - g_mutex_lock(raop_session->list_mutex); - if (!raop_session->play_state.playing) { - gettimeofday(&raop_session->play_state.start_time,NULL); - } - iter = raop_session->raop_list; - while (iter) { - if (!send_control_command(&raop_session->ctrl, iter, - &raop_session->play_state, - error_r)) - goto erexit; - - iter = iter->next; - } - g_mutex_unlock(raop_session->list_mutex); - } - - fill_int(header + 8, raop_session->play_state.sync_src); - - memcpy(raop_session->buffer + raop_session->bufferSize, chunk, copyBytes); - raop_session->bufferSize += copyBytes; - chunk = ((const char *)chunk) + copyBytes; - size -= copyBytes; - - if (!wrap_pcm(raop_session->data + RAOP_HEADER_SIZE, NUMSAMPLES, &count, raop_session->buffer, RAOP_BUFFER_SIZE)) { - g_warning("unable to encode %d bytes properly\n", RAOP_BUFFER_SIZE); - } - - memcpy(raop_session->data, header, RAOP_HEADER_SIZE); - raop_session->data[2] = raop_session->play_state.seq_num >> 8; - raop_session->data[3] = raop_session->play_state.seq_num & 0xff; - raop_session->play_state.seq_num ++; - - fill_int(raop_session->data + 4, raop_session->play_state.rtptime); - raop_session->play_state.rtptime += NUMSAMPLES; - - raop_encrypt(&raop_session->encrypt, raop_session->data + RAOP_HEADER_SIZE, count); - raop_session->wblk_remsize = count + RAOP_HEADER_SIZE; - raop_session->wblk_wsize = 0; - - if (!send_audio_data(raop_session->data_fd, error_r)) - goto erexit; - - raop_session->bufferSize = 0; - } - if (size > 0) { - memcpy(raop_session->buffer + raop_session->bufferSize, chunk, size); - raop_session->bufferSize += size; - } - rval = orig_size; - erexit: - g_mutex_unlock(raop_session->data_mutex); - return rval; -} - -const struct audio_output_plugin raop_output_plugin = { - .name = "raop", - .init = raop_output_init, - .finish = raop_output_finish, - .open = raop_output_open, - .play = raop_output_play, - .cancel = raop_output_cancel, - .pause = raop_output_pause, - .close = raop_output_close, - .mixer_plugin = &raop_mixer_plugin, -}; diff --git a/src/output/raop_output_plugin.h b/src/output/raop_output_plugin.h deleted file mode 100644 index 6aca97836..000000000 --- a/src/output/raop_output_plugin.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * 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., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_RAOP_OUTPUT_PLUGIN_H -#define MPD_RAOP_OUTPUT_PLUGIN_H - -#include <glib.h> - -#include <stdbool.h> - -struct raop_data; - -extern const struct audio_output_plugin raop_output_plugin; - -bool -raop_set_volume(struct raop_data *rd, unsigned volume, GError **error_r); - -int -raop_get_volume(struct raop_data *rd); - -#endif diff --git a/src/output_list.c b/src/output_list.c index e269086cf..ffb0698f1 100644 --- a/src/output_list.c +++ b/src/output_list.c @@ -33,7 +33,6 @@ #include "output/osx_output_plugin.h" #include "output/pipe_output_plugin.h" #include "output/pulse_output_plugin.h" -#include "output/raop_output_plugin.h" #include "output/recorder_output_plugin.h" #include "output/roar_output_plugin.h" #include "output/shout_output_plugin.h" @@ -69,9 +68,6 @@ const struct audio_output_plugin *audio_output_plugins[] = { #ifdef HAVE_OSX &osx_output_plugin, #endif -#ifdef ENABLE_RAOP_OUTPUT - &raop_output_plugin, -#endif #ifdef ENABLE_SOLARIS_OUTPUT &solaris_output_plugin, #endif diff --git a/src/rtsp_client.c b/src/rtsp_client.c deleted file mode 100644 index ea993a163..000000000 --- a/src/rtsp_client.c +++ /dev/null @@ -1,732 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * 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., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Based on the RTSP client by Shiro Ninomiya <shiron@snino.com> - */ - -#include "config.h" -#include "rtsp_client.h" -#include "tcp_socket.h" -#include "fd_util.h" -#include "glib_compat.h" - -#include <assert.h> -#include <stdio.h> -#include <string.h> -#include <unistd.h> -#include <errno.h> -#include <stdlib.h> -#include <sys/time.h> - -#ifdef WIN32 -#include <ws2tcpip.h> -#include <winsock.h> -#else -#include <arpa/inet.h> -#include <sys/socket.h> -#include <netdb.h> -#endif - -/* - * Free all memory associated with key_data - */ -void -free_kd(struct key_data *kd) -{ - struct key_data *iter = kd; - while (iter) { - g_free(iter->key); - g_free(iter->data); - iter = iter->next; - g_free(kd); - kd = iter; - } -} - -/* - * key_data type data look up - */ -char * -kd_lookup(struct key_data *kd, const char *key) -{ - while (kd) { - if (!strcmp(kd->key, key)) { - return kd->data; - } - kd = kd->next; - } - return NULL; -} - -struct rtspcl_data * -rtspcl_open(void) -{ - struct rtspcl_data *rtspcld; - rtspcld = g_new0(struct rtspcl_data, 1); - rtspcld->mutex = g_mutex_new(); - rtspcld->cond = g_cond_new(); - rtspcld->received_lines = g_queue_new(); - rtspcld->useragent = "RTSPClient"; - return rtspcld; -} - -/* bind an opened socket to specified hostname and port. - * if hostname=NULL, use INADDR_ANY. - * if *port=0, use dynamically assigned port - */ -static int bind_host(int sd, char *hostname, unsigned long ulAddr, - unsigned short *port, GError **error_r) -{ - struct sockaddr_in my_addr; - socklen_t nlen = sizeof(struct sockaddr); - struct hostent *h; - - memset(&my_addr, 0, sizeof(my_addr)); - /* use specified hostname */ - if (hostname) { - /* get server IP address (no check if input is IP address or DNS name) */ - h = gethostbyname(hostname); - if (h == NULL) { - if (strstr(hostname, "255.255.255.255") == hostname) { - my_addr.sin_addr.s_addr=-1; - } else { - if ((my_addr.sin_addr.s_addr = inet_addr(hostname)) == 0xFFFFFFFF) { - g_set_error(error_r, rtsp_client_quark(), 0, - "failed to resolve host '%s'", - hostname); - return -1; - } - } - my_addr.sin_family = AF_INET; - } else { - my_addr.sin_family = h->h_addrtype; - memcpy((char *) &my_addr.sin_addr.s_addr, - h->h_addr_list[0], h->h_length); - } - } else { - // if hostname=NULL, use INADDR_ANY - if (ulAddr) - my_addr.sin_addr.s_addr = ulAddr; - else - my_addr.sin_addr.s_addr = htonl(INADDR_ANY); - my_addr.sin_family = AF_INET; - } - - /* bind a specified port */ - my_addr.sin_port = htons(*port); - - if (bind(sd, (struct sockaddr *) &my_addr, sizeof(my_addr)) < 0) { - g_set_error(error_r, rtsp_client_quark(), errno, - "failed to bind socket: %s", - g_strerror(errno)); - return -1; - } - - if (*port == 0) { - getsockname(sd, (struct sockaddr *) &my_addr, &nlen); - *port = ntohs(my_addr.sin_port); - } - - return 0; -} - -/* - * open tcp port - */ -static int -open_tcp_socket(char *hostname, unsigned short *port, - GError **error_r) -{ - int sd; - - /* socket creation */ - sd = socket(AF_INET, SOCK_STREAM, 0); - if (sd < 0) { - g_set_error(error_r, rtsp_client_quark(), errno, - "failed to create TCP socket: %s", - g_strerror(errno)); - return -1; - } - if (bind_host(sd, hostname, 0, port, error_r)) { - close_socket(sd); - return -1; - } - - return sd; -} - -static bool -get_sockaddr_by_host(const char *host, short destport, - struct sockaddr_in *addr, - GError **error_r) -{ - struct hostent *h; - - h = gethostbyname(host); - if (h) { - addr->sin_family = h->h_addrtype; - memcpy((char *) &addr->sin_addr.s_addr, h->h_addr_list[0], h->h_length); - } else { - addr->sin_family = AF_INET; - if ((addr->sin_addr.s_addr=inet_addr(host))==0xFFFFFFFF) { - g_set_error(error_r, rtsp_client_quark(), 0, - "failed to resolve host '%s'", host); - return false; - } - } - addr->sin_port = htons(destport); - return true; -} - -/* - * create tcp connection - * as long as the socket is not non-blocking, this can block the process - * nsport is network byte order - */ -static bool -get_tcp_connect(int sd, struct sockaddr_in dest_addr, GError **error_r) -{ - if (connect(sd, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr))){ - g_usleep(100000); - // try one more time - if (connect(sd, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr))) { - g_set_error(error_r, rtsp_client_quark(), errno, - "failed to connect to %s:%d: %s", - inet_ntoa(dest_addr.sin_addr), - ntohs(dest_addr.sin_port), - g_strerror(errno)); - return false; - } - } - return true; -} - -static bool -get_tcp_connect_by_host(int sd, const char *host, short destport, - GError **error_r) -{ - struct sockaddr_in addr; - - return get_sockaddr_by_host(host, destport, &addr, error_r) && - get_tcp_connect(sd, addr, error_r); -} - -static void -rtsp_client_flush_received(struct rtspcl_data *rtspcld) -{ - char *line; - while ((line = g_queue_pop_head(rtspcld->received_lines)) != NULL) - g_free(line); -} - -static size_t -rtsp_client_socket_data(const void *_data, size_t length, void *ctx) -{ - struct rtspcl_data *rtspcld = ctx; - - g_mutex_lock(rtspcld->mutex); - - if (rtspcld->tcp_socket == NULL) { - g_mutex_unlock(rtspcld->mutex); - return 0; - } - - const bool was_empty = g_queue_is_empty(rtspcld->received_lines); - bool added = false; - const char *data = _data, *end = data + length, *p = data, *eol; - while ((eol = memchr(p, '\n', end - p)) != NULL) { - const char *next = eol + 1; - - if (rtspcld->received_lines->length < 64) { - if (eol > p && eol[-1] == '\r') - --eol; - - g_queue_push_tail(rtspcld->received_lines, - g_strndup(p, eol - p)); - added = true; - } - - p = next; - } - - if (was_empty && added) - g_cond_broadcast(rtspcld->cond); - - g_mutex_unlock(rtspcld->mutex); - - return p - data; -} - -static void -rtsp_client_socket_error(GError *error, void *ctx) -{ - struct rtspcl_data *rtspcld = ctx; - - g_warning("%s", error->message); - g_error_free(error); - - g_mutex_lock(rtspcld->mutex); - - rtsp_client_flush_received(rtspcld); - - struct tcp_socket *s = rtspcld->tcp_socket; - rtspcld->tcp_socket = NULL; - - g_cond_broadcast(rtspcld->cond); - - g_mutex_unlock(rtspcld->mutex); - - if (s != NULL) - tcp_socket_free(s); -} - -static void -rtsp_client_socket_disconnected(void *ctx) -{ - struct rtspcl_data *rtspcld = ctx; - - g_mutex_lock(rtspcld->mutex); - - rtsp_client_flush_received(rtspcld); - - struct tcp_socket *s = rtspcld->tcp_socket; - rtspcld->tcp_socket = NULL; - - g_cond_broadcast(rtspcld->cond); - - g_mutex_unlock(rtspcld->mutex); - - if (s != NULL) - tcp_socket_free(s); -} - -static const struct tcp_socket_handler rtsp_client_socket_handler = { - .data = rtsp_client_socket_data, - .error = rtsp_client_socket_error, - .disconnected = rtsp_client_socket_disconnected, -}; - -bool -rtspcl_connect(struct rtspcl_data *rtspcld, const char *host, short destport, - const char *sid, GError **error_r) -{ - assert(rtspcld->tcp_socket == NULL); - - unsigned short myport = 0; - struct sockaddr_in name; - socklen_t namelen = sizeof(name); - - int fd = open_tcp_socket(NULL, &myport, error_r); - if (fd < 0) - return false; - - if (!get_tcp_connect_by_host(fd, host, destport, error_r)) - return false; - - getsockname(fd, (struct sockaddr*)&name, &namelen); - memcpy(&rtspcld->local_addr, &name.sin_addr,sizeof(struct in_addr)); - sprintf(rtspcld->url, "rtsp://%s/%s", inet_ntoa(name.sin_addr), sid); - getpeername(fd, (struct sockaddr*)&name, &namelen); - memcpy(&rtspcld->host_addr, &name.sin_addr, sizeof(struct in_addr)); - - rtspcld->tcp_socket = tcp_socket_new(fd, &rtsp_client_socket_handler, - rtspcld); - - return true; -} - -static void -rtspcl_disconnect(struct rtspcl_data *rtspcld) -{ - g_mutex_lock(rtspcld->mutex); - rtsp_client_flush_received(rtspcld); - g_mutex_unlock(rtspcld->mutex); - - if (rtspcld->tcp_socket != NULL) { - tcp_socket_free(rtspcld->tcp_socket); - rtspcld->tcp_socket = NULL; - } -} - -static void -rtspcl_remove_all_exthds(struct rtspcl_data *rtspcld) -{ - free_kd(rtspcld->exthds); - rtspcld->exthds = NULL; -} - -void -rtspcl_close(struct rtspcl_data *rtspcld) -{ - rtspcl_disconnect(rtspcld); - g_queue_free(rtspcld->received_lines); - rtspcl_remove_all_exthds(rtspcld); - g_free(rtspcld->session); - g_cond_free(rtspcld->cond); - g_mutex_free(rtspcld->mutex); - g_free(rtspcld); -} - -void -rtspcl_add_exthds(struct rtspcl_data *rtspcld, const char *key, char *data) -{ - struct key_data *new_kd; - new_kd = g_new(struct key_data, 1); - new_kd->key = g_strdup(key); - new_kd->data = g_strdup(data); - new_kd->next = NULL; - if (!rtspcld->exthds) { - rtspcld->exthds = new_kd; - } else { - struct key_data *iter = rtspcld->exthds; - while (iter->next) { - iter = iter->next; - } - iter->next = new_kd; - } -} - -/* - * read one line from the file descriptor - * timeout: msec unit, -1 for infinite - * if CR comes then following LF is expected - * returned string in line is always null terminated, maxlen-1 is maximum string length - */ -static int -read_line(struct rtspcl_data *rtspcld, char *line, int maxlen, - int timeout) -{ - g_mutex_lock(rtspcld->mutex); - - GTimeVal end_time; - if (timeout >= 0) { - g_get_current_time(&end_time); - - end_time.tv_sec += timeout / 1000; - timeout %= 1000; - end_time.tv_usec = timeout * 1000; - if (end_time.tv_usec > 1000000) { - end_time.tv_usec -= 1000000; - ++end_time.tv_sec; - } - } - - while (true) { - if (!g_queue_is_empty(rtspcld->received_lines)) { - /* success, copy to buffer */ - - char *p = g_queue_pop_head(rtspcld->received_lines); - g_mutex_unlock(rtspcld->mutex); - - g_strlcpy(line, p, maxlen); - g_free(p); - - return strlen(line); - } - - if (rtspcld->tcp_socket == NULL) { - /* error */ - g_mutex_unlock(rtspcld->mutex); - return -1; - } - - if (timeout < 0) { - g_cond_wait(rtspcld->cond, rtspcld->mutex); - } else if (!g_cond_timed_wait(rtspcld->cond, rtspcld->mutex, - &end_time)) { - g_mutex_unlock(rtspcld->mutex); - return 0; - } - } -} - -/* - * send RTSP request, and get response if it's needed - * if this gets a success, *kd is allocated or reallocated (if *kd is not NULL) - */ -bool -exec_request(struct rtspcl_data *rtspcld, const char *cmd, - const char *content_type, const char *content, - int get_response, - const struct key_data *hds, struct key_data **kd, - GError **error_r) -{ - char line[1024]; - char req[1024]; - char reql[128]; - const char delimiters[] = " "; - char *token, *dp; - int dsize = 0; - int timeout = 5000; // msec unit - - if (!rtspcld) { - g_set_error_literal(error_r, rtsp_client_quark(), 0, - "not connected"); - return false; - } - - sprintf(req, "%s %s RTSP/1.0\r\nCSeq: %d\r\n", cmd, rtspcld->url, ++rtspcld->cseq ); - - if ( rtspcld->session != NULL ) { - sprintf(reql,"Session: %s\r\n", rtspcld->session ); - g_strlcat(req, reql, sizeof(req)); - } - - const struct key_data *hd_iter = hds; - while (hd_iter) { - sprintf(reql, "%s: %s\r\n", hd_iter->key, hd_iter->data); - g_strlcat(req, reql, sizeof(req)); - hd_iter = hd_iter->next; - } - - if (content_type && content) { - sprintf(reql, "Content-Type: %s\r\nContent-Length: %d\r\n", - content_type, (int) strlen(content)); - g_strlcat(req, reql, sizeof(req)); - } - - sprintf(reql, "User-Agent: %s\r\n", rtspcld->useragent); - g_strlcat(req, reql, sizeof(req)); - - hd_iter = rtspcld->exthds; - while (hd_iter) { - sprintf(reql, "%s: %s\r\n", hd_iter->key, hd_iter->data); - g_strlcat(req, reql, sizeof(req)); - hd_iter = hd_iter->next; - } - g_strlcat(req, "\r\n", sizeof(req)); - - if (content_type && content) - g_strlcat(req, content, sizeof(req)); - - if (!tcp_socket_send(rtspcld->tcp_socket, req, strlen(req))) { - g_set_error(error_r, rtsp_client_quark(), errno, - "write error: %s", - g_strerror(errno)); - return false; - } - - if (!get_response) return true; - - if (read_line(rtspcld, line, sizeof(line), timeout) <= 0) { - g_set_error_literal(error_r, rtsp_client_quark(), 0, - "request failed"); - return false; - } - - token = strtok(line, delimiters); - token = strtok(NULL, delimiters); - if (token == NULL) { - g_set_error_literal(error_r, rtsp_client_quark(), 0, - "request failed"); - return false; - } - - if (strcmp(token, "200") != 0) { - g_set_error(error_r, rtsp_client_quark(), 0, - "request failed: %s", token); - return false; - } - - /* if the caller isn't interested in response headers, put - them on the trash, which is freed before returning from - this function */ - struct key_data *trash = NULL; - if (kd == NULL) - kd = &trash; - - struct key_data *cur_kd = *kd; - - struct key_data *new_kd = NULL; - while (read_line(rtspcld, line, sizeof(line), timeout) > 0) { - timeout = 1000; // once it started, it shouldn't take a long time - if (new_kd != NULL && line[0] == ' ') { - const char *j = line; - while (*j == ' ') - ++j; - - dsize += strlen(j); - new_kd->data = g_realloc(new_kd->data, dsize); - strcat(new_kd->data, j); - continue; - } - dp = strstr(line, ":"); - if (!dp) { - free_kd(*kd); - *kd = NULL; - - g_set_error_literal(error_r, rtsp_client_quark(), 0, - "request failed, bad header"); - return false; - } - - *dp++ = 0; - new_kd = g_new(struct key_data, 1); - new_kd->key = g_strdup(line); - dsize = strlen(dp) + 1; - new_kd->data = g_strdup(dp); - new_kd->next = NULL; - if (cur_kd == NULL) { - cur_kd = *kd = new_kd; - } else { - cur_kd->next = new_kd; - cur_kd = new_kd; - } - } - - free_kd(trash); - - return true; -} - -bool -rtspcl_set_parameter(struct rtspcl_data *rtspcld, const char *parameter, - GError **error_r) -{ - return exec_request(rtspcld, "SET_PARAMETER", "text/parameters", - parameter, 1, NULL, NULL, error_r); -} - -void -rtspcl_set_useragent(struct rtspcl_data *rtspcld, const char *name) -{ - rtspcld->useragent = name; -} - -bool -rtspcl_announce_sdp(struct rtspcl_data *rtspcld, const char *sdp, - GError **error_r) -{ - return exec_request(rtspcld, "ANNOUNCE", "application/sdp", sdp, 1, - NULL, NULL, error_r); -} - -bool -rtspcl_setup(struct rtspcl_data *rtspcld, struct key_data **kd, - int control_port, int ntp_port, - GError **error_r) -{ - struct key_data *rkd = NULL, hds; - const char delimiters[] = ";"; - char *buf = NULL; - char *token, *pc; - int rval = false; - - static char transport_key[] = "Transport"; - - char transport_value[256]; - snprintf(transport_value, sizeof(transport_value), - "RTP/AVP/UDP;unicast;interleaved=0-1;mode=record;control_port=%d;timing_port=%d", - control_port, ntp_port); - - hds.key = transport_key; - hds.data = transport_value; - hds.next = NULL; - if (!exec_request(rtspcld, "SETUP", NULL, NULL, 1, - &hds, &rkd, error_r)) - return false; - - if (!(rtspcld->session = g_strdup(kd_lookup(rkd, "Session")))) { - g_set_error_literal(error_r, rtsp_client_quark(), 0, - "no session in response"); - goto erexit; - } - if (!(rtspcld->transport = kd_lookup(rkd, "Transport"))) { - g_set_error_literal(error_r, rtsp_client_quark(), 0, - "no transport in response"); - goto erexit; - } - buf = g_strdup(rtspcld->transport); - token = strtok(buf, delimiters); - rtspcld->server_port = 0; - rtspcld->control_port = 0; - while (token) { - if ((pc = strstr(token, "="))) { - *pc = 0; - if (!strcmp(token,"server_port")) { - rtspcld->server_port=atoi(pc + 1); - } - if (!strcmp(token,"control_port")) { - rtspcld->control_port=atoi(pc + 1); - } - } - token = strtok(NULL, delimiters); - } - if (rtspcld->server_port == 0) { - g_set_error_literal(error_r, rtsp_client_quark(), 0, - "no server_port in response"); - goto erexit; - } - if (rtspcld->control_port == 0) { - g_set_error_literal(error_r, rtsp_client_quark(), 0, - "no control_port in response"); - goto erexit; - } - rval = true; - erexit: - g_free(buf); - - if (!rval || kd == NULL) { - free_kd(rkd); - rkd = NULL; - } - - if (kd != NULL) - *kd = rkd; - - return rval; -} - -bool -rtspcl_record(struct rtspcl_data *rtspcld, - int seq_num, int rtptime, - GError **error_r) -{ - if (!rtspcld->session) { - g_set_error_literal(error_r, rtsp_client_quark(), 0, - "no session in progress"); - return false; - } - - char buf[128]; - sprintf(buf, "seq=%d,rtptime=%u", seq_num, rtptime); - - struct key_data rtp; - static char rtp_key[] = "RTP-Info"; - rtp.key = rtp_key; - rtp.data = buf; - rtp.next = NULL; - - struct key_data range; - static char range_key[] = "Range"; - range.key = range_key; - static char range_value[] = "npt=0-"; - range.data = range_value; - range.next = &rtp; - - return exec_request(rtspcld, "RECORD", NULL, NULL, 1, &range, - NULL, error_r); -} - -char * -rtspcl_local_ip(struct rtspcl_data *rtspcld) -{ - return inet_ntoa(rtspcld->local_addr); -} diff --git a/src/rtsp_client.h b/src/rtsp_client.h deleted file mode 100644 index 21660e609..000000000 --- a/src/rtsp_client.h +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * 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., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Based on the RTSP client by Shiro Ninomiya <shiron@snino.com> - */ - -#ifndef MPD_RTSP_CLIENT_H -#define MPD_RTSP_CLIENT_H - -#include <stdbool.h> -#include <glib.h> - -#ifdef WIN32 -#include <ws2tcpip.h> -#include <winsock.h> -#else -#include <netinet/in.h> -#endif - -struct key_data { - char *key; - char *data; - struct key_data *next; -}; - -struct rtspcl_data { - GMutex *mutex; - GCond *cond; - - GQueue *received_lines; - - struct tcp_socket *tcp_socket; - - char url[128]; - int cseq; - struct key_data *exthds; - char *session; - char *transport; - unsigned short server_port; - unsigned short control_port; - struct in_addr host_addr; - struct in_addr local_addr; - const char *useragent; - -}; - -/** - * The quark used for GError.domain. - */ -static inline GQuark -rtsp_client_quark(void) -{ - return g_quark_from_static_string("rtsp_client"); -} - -void -free_kd(struct key_data *kd); - -char * -kd_lookup(struct key_data *kd, const char *key); - -G_GNUC_MALLOC -struct rtspcl_data * -rtspcl_open(void); - -bool -rtspcl_connect(struct rtspcl_data *rtspcld, const char *host, short destport, - const char *sid, GError **error_r); - -void -rtspcl_close(struct rtspcl_data *rtspcld); - -void -rtspcl_add_exthds(struct rtspcl_data *rtspcld, const char *key, char *data); - -bool -exec_request(struct rtspcl_data *rtspcld, const char *cmd, - const char *content_type, const char *content, - int get_response, - const struct key_data *hds, struct key_data **kd, - GError **error_r); - -bool -rtspcl_set_parameter(struct rtspcl_data *rtspcld, const char *parameter, - GError **error_r); - -void -rtspcl_set_useragent(struct rtspcl_data *rtspcld, const char *name); - -bool -rtspcl_announce_sdp(struct rtspcl_data *rtspcld, const char *sdp, - GError **error_r); - -bool -rtspcl_setup(struct rtspcl_data *rtspcld, struct key_data **kd, - int control_port, int ntp_port, - GError **error_r); - -bool -rtspcl_record(struct rtspcl_data *rtspcld, - int seq_num, int rtptime, - GError **error_r); - -char * -rtspcl_local_ip(struct rtspcl_data *rtspcld); - -#endif diff --git a/test/read_mixer.c b/test/read_mixer.c index 0a0719460..f6de8177d 100644 --- a/test/read_mixer.c +++ b/test/read_mixer.c @@ -83,25 +83,6 @@ roar_output_set_volume(G_GNUC_UNUSED struct roar *roar, #endif -#ifdef ENABLE_RAOP_OUTPUT -#include "output/raop_output_plugin.h" - -bool -raop_set_volume(G_GNUC_UNUSED struct raop_data *rd, - G_GNUC_UNUSED unsigned volume, - G_GNUC_UNUSED GError **error_r) -{ - return false; -} - -int -raop_get_volume(G_GNUC_UNUSED struct raop_data *rd) -{ - return -1; -} - -#endif - void event_pipe_emit(G_GNUC_UNUSED enum pipe_event event) { diff --git a/test/run_ntp_server.c b/test/run_ntp_server.c deleted file mode 100644 index 842d1852e..000000000 --- a/test/run_ntp_server.c +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * 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., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "ntp_server.h" -#include "signals.h" -#include "io_thread.h" - -#include <glib.h> - -#include <stdlib.h> -#include <string.h> -#include <errno.h> -#include <unistd.h> - -#ifdef WIN32 -#include <ws2tcpip.h> -#include <winsock.h> -#else -#include <sys/socket.h> -#include <netdb.h> -#include <netinet/in.h> -#include <arpa/inet.h> -#endif - -void -on_quit(void) -{ - io_thread_quit(); -} - -int -main(G_GNUC_UNUSED int argc, G_GNUC_UNUSED char **argv) -{ - g_thread_init(NULL); - signals_init(); - io_thread_init(); - - struct ntp_server ntp; - ntp_server_init(&ntp); - - GError *error = NULL; - if (!ntp_server_open(&ntp, &error)) { - io_thread_deinit(); - g_printerr("%s\n", error->message); - g_error_free(error); - return EXIT_FAILURE; - } - - io_thread_run(); - - ntp_server_close(&ntp); - io_thread_deinit(); - return EXIT_SUCCESS; -} |