diff --git a/Makefile.am b/Makefile.am index 6b0f44c..29c9428 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,3 +1,3 @@ -SUBDIRS = src data +SUBDIRS = src data contribs EXTRA_DIST = autogen.sh diff --git a/configure.ac b/configure.ac index 5b67a77..bb7c106 100644 --- a/configure.ac +++ b/configure.ac @@ -1,9 +1,14 @@ AC_PREREQ(2.59) -AC_INIT(gmediarender, 0.0.8, https://github.com/hzeller/gmrender-resurrect) +AC_INIT([gmediarender], 0.0.8, https://github.com/hzeller/gmrender-resurrect) AC_CONFIG_AUX_DIR(config) AC_CONFIG_SRCDIR(src/main.c) AC_CONFIG_HEADERS(config.h) -AM_INIT_AUTOMAKE([1.8 dist-bzip2 no-dist-gzip check-news]) +AC_CONFIG_MACRO_DIRS([m4]) +AM_INIT_AUTOMAKE([1.8 dist-bzip2 no-dist-gzip check-news foreign]) + +AC_SUBST(GMRENDER_GST_VERSION, [0.0.8]) + +LT_INIT AC_SYS_LARGEFILE @@ -106,6 +111,27 @@ if test x$HAVE_LIBUPNP = xyes; then fi AC_SUBST(HAVE_LIBUPNP) +PKG_CHECK_MODULES(ALSA, alsa, HAVE_ALSA=yes, HAVE_ALSA=no) +if test x$HAVE_ALSA = xyes; then + AC_DEFINE(HAVE_ALSA, , [Use Alsa]) +fi +AC_SUBST(HAVE_ALSA) +AM_CONDITIONAL(HAVE_ALSA, test x$HAVE_ALSA = xyes) + +AC_ARG_WITH( mpg123, + AC_HELP_STRING([--without-mpg123],[compile without MPG123 support]), + try_mpg123=$withval, try_mpg123=yes ) +HAVE_MPG123=no +if test x$try_gstreamer = xyes; then + dnl check for GStreamer + PKG_CHECK_MODULES(MPG123, libmpg123, HAVE_MPG123=yes, HAVE_MPG123=no) +fi +if test x$HAVE_MPG123 = xyes; then + AC_DEFINE(HAVE_MPG123, , [Use mpg123]) +fi +AC_SUBST(HAVE_MPG123) +AM_CONDITIONAL(HAVE_MPG123, test x$HAVE_MPG123 = xyes) + # Checks for header files. AC_HEADER_STDC @@ -118,6 +144,7 @@ fi AC_CONFIG_FILES([Makefile src/Makefile + contribs/Makefile data/Makefile]) AC_OUTPUT diff --git a/contribs/Makefile.am b/contribs/Makefile.am new file mode 100644 index 0000000..da89bb1 --- /dev/null +++ b/contribs/Makefile.am @@ -0,0 +1,22 @@ +if HAVE_MPG123 +pkglib_LTLIBRARIES = gmrender_mpg123.la +endif + +gmrender_mpg123_la_SOURCES = \ + sound_module.c sound_module.h \ + sound_alsa.c \ + webclient.c \ + output_mpg123.c + +GMRENDER_MPG123_VERSION=0.0.1 + +MOD_MAJOR_VERSION=$(word 1,$(subst ., ,$(GMRENDER_MPG123_VERSION))) +MOD_MINOR_VERSION=$(word 2,$(subst ., ,$(GMRENDER_MPG123_VERSION))) +MOD_MICRO_VERSION=$(word 3,$(subst ., ,$(GMRENDER_MPG123_VERSION))) + +gmrender_mpg123_la_CFLAGS = $(GLIB_CFLAGS) $(ALSA_CFLAGS) -I ../src +gmrender_mpg123_la_CFLAGS += -DMOD_MAJOR_VERSION=$(MOD_MAJOR_VERSION) +gmrender_mpg123_la_CFLAGS += -DMOD_MINOR_VERSION=$(MOD_MINOR_VERSION) +gmrender_mpg123_la_CFLAGS += -DMOD_MICRO_VERSION=$(MOD_MICRO_VERSION) +gmrender_mpg123_la_LIBADD = $(GLIB_LIBS) $(ALSA_LIBS) -lmpg123 +gmrender_mpg123_la_LDFLAGS = -module -release $(GMRENDER_MPG123_VERSION) diff --git a/contribs/output_mpg123.c b/contribs/output_mpg123.c new file mode 100644 index 0000000..8aa5f21 --- /dev/null +++ b/contribs/output_mpg123.c @@ -0,0 +1,393 @@ +/* output_mpg123.c - Output module for mpg123 + * + * Copyright (C) 2014-2019 Mar Chalain + * + * This file is part of GMediaRender. + * + * GMediaRender 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. + * + * GMediaRender 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 Library General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GMediaRender; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "output_module.h" +#include "sound_module.h" + +#include "logging.h" +#include "upnp_connmgr.h" + +#include "webclient.h" + +enum e_state +{ + STOPPED, + PLAYING, + PAUSING, + HALTED, +}; +enum e_state g_state = STOPPED; + +typedef struct st_output_mpg123_uri output_mpg123_uri_t; +struct st_output_mpg123_uri +{ + char *uri; + enum e_state state; + size_t position; + struct http_info info; + output_mpg123_uri_t *next; +}; + + +static output_mpg123_uri_t *g_first_uri; +static output_mpg123_uri_t *g_current_uri; +static output_transition_cb_t g_callback; + +static mpg123_pars *g_mpg123_pars; +static mpg123_handle *g_mpg123_handle = NULL; + +static pthread_mutex_t g_mutex_control; +static pthread_cond_t g_cond_control; + +static const char *g_cmd_mime = "audio/mpeg"; +static const struct sound_module *g_sound_api; + +static int +output_mpg123_init(void) +{ + int ret = -1; + + if (g_cmd_mime) + { + const char *mime = g_cmd_mime; + const char *nextmime = mime; + for (;*mime; mime++) + { + if (*mime == ',') + { + *(char *)mime = '\0'; + register_mime_type(nextmime); + nextmime = mime + 1; + } + } + register_mime_type(nextmime); + } + pthread_mutex_init(&g_mutex_control, NULL); + pthread_cond_init(&g_cond_control, NULL); + + ret = mpg123_init(); + g_mpg123_pars = mpg123_new_pars(&ret); + const char **decoderslist = mpg123_decoders(); + g_mpg123_handle = mpg123_new(decoderslist[0], &ret); + + if (!ret) + { + g_sound_api = sound_module_get(); + if (g_sound_api == NULL) + { + Log_error("mpg123", "sound module not found"); + ret = -1; + } + } + return ret; +} + +static void +output_mpg123_set_uri(const char *uri, + output_update_meta_cb_t meta_cb) +{ + struct st_output_mpg123_uri *entry = malloc(sizeof(*entry)); + memset(entry, 0 ,sizeof(*entry)); + entry->uri = strdup(uri); + if (g_state == PLAYING && g_current_uri != NULL) + { + g_current_uri->next = entry; + } + else + { + entry->next = g_first_uri; + g_first_uri = entry; + } +}; + +static void +output_mpg123_set_next_uri(const char *uri) +{ + struct st_output_mpg123_uri *entry = malloc(sizeof(*entry)); + memset(entry, 0 ,sizeof(*entry)); + entry->uri = strdup(uri); + if (g_first_uri == NULL) + { + g_first_uri = entry; + } + else + { + struct st_output_mpg123_uri *it = g_first_uri; + while (it->next != NULL) it = it->next; + it->next = entry; + } +} + +static int +output_mpg123_openstream(int fdin, int *channels, int *encoding, long *rate, long *buffsize) +{ + if(mpg123_open_fd(g_mpg123_handle, fdin) != MPG123_OK) + { + return -1; + } + + if (mpg123_getformat(g_mpg123_handle, rate, channels, encoding) != MPG123_OK) + { + return -1; + } + mpg123_format_none(g_mpg123_handle); + mpg123_format(g_mpg123_handle, *rate, *channels, *encoding); + + *buffsize = mpg123_outblock(g_mpg123_handle); + return 0; +} + +static void* +thread_play(void *arg) +{ + while (g_state != HALTED) + { + pthread_mutex_lock(&g_mutex_control); + while (g_state == STOPPED) + { + pthread_cond_wait(&g_cond_control, &g_mutex_control); + } + pthread_mutex_unlock(&g_mutex_control); + if (g_current_uri == NULL || g_current_uri->uri == NULL) + continue; + + int fdin = http_get(g_current_uri->uri, &g_current_uri->info); + if (fdin < 0) + break; + + int channels = 0, encoding = 0; + long rate = 0, buffsize = 0; + if (output_mpg123_openstream(fdin, &channels, &encoding, &rate, &buffsize)) + { + break; + } + unsigned char *buffer; + buffer = malloc(buffsize); + + g_sound_api->open(channels, encoding, rate); + + int err = MPG123_OK; + do + { + pthread_mutex_lock(&g_mutex_control); + while (g_state == PAUSING) + { + pthread_cond_wait(&g_cond_control, &g_mutex_control); + } + /** + * stop is requested from the controler + **/ + if (g_state == STOPPED) + { + g_current_uri->position = 0; + pthread_mutex_unlock(&g_mutex_control); + break; + } + pthread_mutex_unlock(&g_mutex_control); + size_t done = 0; + err = mpg123_read( g_mpg123_handle, buffer, buffsize, &done ); + g_current_uri->position += done; + if (err == MPG123_OK) + { + err = (g_sound_api->write(buffer, buffsize) >= 0)? MPG123_OK : MPG123_ERR; + } + } while (err == MPG123_OK); + mpg123_close(g_mpg123_handle); + g_sound_api->close(); + + struct st_output_mpg123_uri *it = g_first_uri; + if (it == g_current_uri) + { + g_first_uri = g_first_uri->next; + } + else + { + while (it->next != g_current_uri) it = it->next; + it->next = it->next->next; + } + + g_current_uri->position = g_current_uri->info.length; + /** + * prepare the next stream + **/ + struct st_output_mpg123_uri *entry = g_current_uri->next; + free(g_current_uri->uri); + free(g_current_uri); + if (!entry) + { + (*g_callback)(PLAY_STOPPED); + g_current_uri = NULL; + } + else + { + g_current_uri = entry; + g_current_uri->position = 0; + (*g_callback)(PLAY_STARTED_NEXT_STREAM); + } + } + return NULL; +} + +/** +static int +output_mpg123_loop() +{ + thread_play(NULL); + return 0; +} +**/ + +static int +output_mpg123_play(output_transition_cb_t callback) +{ + g_callback = callback; + g_state = PLAYING; + if (!g_current_uri) + { + struct st_output_mpg123_uri *entry = g_first_uri; + if (entry) + { + g_current_uri = entry; + g_current_uri->position = 0; + + pthread_t thread; + pthread_create(&thread, NULL, thread_play, NULL); + } + } + pthread_cond_signal(&g_cond_control); + return 0; +} + +static int +output_mpg123_stop(void) +{ + g_state = STOPPED; + pthread_cond_signal(&g_cond_control); + return 0; +} + +static int +output_mpg123_pause(void) +{ + g_state = PAUSING; + pthread_cond_signal(&g_cond_control); + return 0; +} + +static int +output_mpg123_seek(int64_t position_nanos) +{ + return 0; +} + +static int +output_mpg123_get_position(int64_t *track_duration, + int64_t *track_pos) +{ + if (g_current_uri == NULL) + { + *track_duration = 0; + *track_pos = 0; + } + else + { + *track_duration = g_current_uri->info.length; + *track_pos = g_current_uri->position; + } + return 0; +} + +static int +output_mpg123_getvolume(float *value) +{ + if (g_sound_api->get_volume) + return g_sound_api->get_volume(value); + return 0; +} +static int +output_mpg123_setvolume(float value) +{ + if (g_sound_api->set_volume) + return g_sound_api->set_volume(value); + return 0; +} +static int +output_mpg123_getmute(int *value) +{ + if (g_sound_api->get_mute) + return g_sound_api->get_mute(value); + return 0; +} +static int +output_mpg123_setmute(int value) +{ + if (g_sound_api->set_mute) + return g_sound_api->set_mute(value); + return 0; +} + + +struct output_module mpg123_output = { + .shortname = "mpg123", + .description = "daemon framework", + .init = output_mpg123_init, + .set_uri = output_mpg123_set_uri, + .set_next_uri= output_mpg123_set_next_uri, + .play = output_mpg123_play, + .stop = output_mpg123_stop, + .pause = output_mpg123_pause, + .seek = output_mpg123_seek, + .get_position = output_mpg123_get_position, + .get_volume = output_mpg123_getvolume, + .set_volume = output_mpg123_setvolume, + .get_mute = output_mpg123_getmute, + .set_mute = output_mpg123_setmute, +}; + +void output_mpg123_initlib(void) __attribute__((constructor)); + +void output_mpg123_initlib(void) +{ + output_append_module(&mpg123_output); +} diff --git a/contribs/sound_alsa.c b/contribs/sound_alsa.c new file mode 100644 index 0000000..88630e0 --- /dev/null +++ b/contribs/sound_alsa.c @@ -0,0 +1,123 @@ +/* output_alsa.c - Sound module for alsa + * + * Copyright (C) 2014-2019 Mar Chalain + * + * This file is part of GMediaRender. + * + * GMediaRender 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. + * + * GMediaRender 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 Library General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GMediaRender; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "logging.h" +#include "sound_module.h" + +struct sound_alsa_global_s +{ + const char *cmd_card; + snd_pcm_t *pcm; + int samplesize; + struct fifo_s *fifo; +}; +struct sound_alsa_global_s g = { + .cmd_card = "default", + .pcm = NULL, + .fifo = NULL +}; + +static int +sound_alsa_open(int channels, int encoding, unsigned int rate) +{ + int ret; + + ret = snd_pcm_open(&g.pcm, g.cmd_card, SND_PCM_STREAM_PLAYBACK, 0); + + if (ret == -1 || g.pcm == NULL) + return -1; + + snd_pcm_hw_params_t *hw_params; + ret = snd_pcm_hw_params_malloc(&hw_params); + ret = snd_pcm_hw_params_any(g.pcm, hw_params); + ret = snd_pcm_hw_params_set_access(g.pcm, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED); + snd_pcm_format_t pcm_format; + switch (encoding) + { + case MPG123_ENC_SIGNED_32: + pcm_format = SND_PCM_FORMAT_S32_LE; + g.samplesize = 4 * 2; + break; + case MPG123_ENC_SIGNED_16: + default: + pcm_format = SND_PCM_FORMAT_S16_LE; + g.samplesize = 2 * 2; + break; + } + ret = snd_pcm_hw_params_set_format(g.pcm, hw_params, pcm_format); + ret = snd_pcm_hw_params_set_rate_near(g.pcm, hw_params, &rate, NULL); + ret = snd_pcm_hw_params_set_channels(g.pcm, hw_params, channels); + + ret = snd_pcm_hw_params(g.pcm, hw_params); + ret = snd_pcm_prepare(g.pcm); + + return 0; +} + +static ssize_t +sound_alsa_write(unsigned char *buffer, ssize_t size) +{ + snd_pcm_sframes_t ret; + ret = snd_pcm_writei(g.pcm, buffer, size / g.samplesize); + if (ret == -EPIPE) + ret = snd_pcm_recover(g.pcm, ret, 0); + return ret * g.samplesize; +} + +static int +sound_alsa_close(void) +{ + return snd_pcm_close(g.pcm); +} + +struct sound_module const *g_sound_alsa = &(struct sound_module) +{ + .name = "alsa", + .open = sound_alsa_open, + .write = sound_alsa_write, + .close = sound_alsa_close, + .get_volume = NULL, + .set_volume = NULL, + .get_mute = NULL, + .set_mute = NULL, +}; diff --git a/contribs/sound_module.c b/contribs/sound_module.c new file mode 100644 index 0000000..fa3b5d3 --- /dev/null +++ b/contribs/sound_module.c @@ -0,0 +1,43 @@ +/* output_module.c - Sound module frontend + * + * Copyright (C) 2014 - 2019 Marc Chalain + * + * This file is part of GMediaRender. + * + * uplaymusic 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. + * + * uplaymusic 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 Library General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GMediaRender; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + * + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#define _GNU_SOURCE +#include +#include +#include + +#include "sound_module.h" + +#ifdef HAVE_ALSA +extern const struct sound_module *g_sound_alsa; +#endif + +const struct sound_module *sound_module_get(void) +{ +#ifdef HAVE_ALSA + return g_sound_alsa; +#endif +} diff --git a/src/output_gstreamer.h b/contribs/sound_module.h similarity index 52% rename from src/output_gstreamer.h rename to contribs/sound_module.h index 6e89010..1708318 100644 --- a/src/output_gstreamer.h +++ b/contribs/sound_module.h @@ -1,6 +1,6 @@ -/* output_gstreamer.h - Definitions for GStreamer output module +/* sound_module.h - Audio sink module * - * Copyright (C) 2005-2007 Ivo Clarysse + * Copyright (C) 2014-2019 Marc Chalain * * This file is part of GMediaRender. * @@ -15,15 +15,26 @@ * GNU Library General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with GMediaRender; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * along with GMediaRender; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ -#ifndef _OUTPUT_GSTREAMER_H -#define _OUTPUT_GSTREAMER_H +#ifndef _SOUND_MODULE_H +#define _SOUND_MODULE_H +struct sound_module +{ + const char *name; + int (*open)(int channels, int encoding, unsigned int rate); + ssize_t (*write)(unsigned char *buffer, ssize_t size); + int (*close)(void); + int (*get_volume)(float *); + int (*set_volume)(float); + int (*get_mute)(int *); + int (*set_mute)(int); +}; -extern struct output_module gstreamer_output; +const struct sound_module *sound_module_get(void); -#endif /* _OUTPUT_GSTREAMER_H */ +#endif diff --git a/contribs/webclient.c b/contribs/webclient.c new file mode 100644 index 0000000..7c6ccab --- /dev/null +++ b/contribs/webclient.c @@ -0,0 +1,229 @@ +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include "webclient.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "logging.h" + +static int http_get_transaction(int fd, struct http_info *info) +{ + int ret = -1; + int len; + + /** + * generate the request + **/ + char *wbuff; + wbuff = malloc(1024 + 1); + len = 0; + const char *method = "GET"; + const char *page = "/index.html"; + if (info->method != NULL) + method = info->method; + if (info->uri != NULL) + page = info->uri; + sprintf(wbuff+len, "%s %s HTTP/1.0\r\n", method, page); + len += strlen(page) + 15; +/* + * sprintf(wbuff+len, "User-Agent: mpg123/1.12.1\r\n"); + len += 27; + sprintf(wbuff+len, "Host: 10.1.2.9:49152\r\n"); + len += 22; + sprintf(wbuff+len, "Accept: audio/mpeg, audio/x-mpeg, audio/mp3, audio/x-mp3, audio/mpeg3, audio/x-mpeg3, audio/mpg, audio/x-mpg, audio/x-mpegaudio, audio/mpegurl, audio/mpeg-url, audio/x-mpegurl, audio/x-scpls, audio/scpls, application/pls\r\n"); + len += 227; +*/ + sprintf(wbuff+len, "\r\n"); + len += 2; + + /** + * send the request + **/ + ret = write(fd, wbuff, len); + free(wbuff); + + /** + * look for bytes available on the connection + **/ + //int ret; + fd_set rfds; + int maxfd; + + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + maxfd = fd +1; + + do + { + ret = select(maxfd, &rfds, NULL, NULL, NULL); + } while (ret <= 0 || !FD_ISSET(fd, &rfds)); + + /** + * allocate buffer with enought space for the header + **/ + char rbuff[1024]; + char *it = rbuff; + memset(rbuff, 0, sizeof(rbuff)); + while (ret > 0) + { + char tbuff[1]; + len = 1; + + ret = recv(fd, tbuff, len, 0); + *it = tbuff[0]; + if (ret < 0) + { + //LOG_ERROR("read from socket error %d %s", ret, strerror(errno)); + return -1; + } + if (strstr(rbuff, "\r\n\r\n")) + { + Log_info("webclient", "header:\n%s", rbuff); + break; + } + it += ret; + if (it == rbuff + sizeof(rbuff)) + { + while (*it != '\n') it--; + int len = rbuff + sizeof(rbuff) - it; + memcpy(rbuff, it, len); + it = rbuff + len; + } + } + /** + * parse the header + **/ + char *value; + if ((value = strstr(rbuff, "Content-Length: "))) + { + sscanf(value,"Content-Length: %u[^\r]", &info->length); + } + if ((value = strstr(rbuff, "Content-Type: "))) + { + sscanf(value,"Content-Type: %99[^\r]", info->mime); + } + + return ret; +} + +int +http_get(char *uri, struct http_info *info) +{ + int fd = -1; + int port = 0; + char proto[10]; + char ip[100]; + char page[200]; + int err = -1; + + memset(proto, 0, 10); + memset(ip, 0, 100); + memset(page, 0, 100); + port = 80; + + page[0]='/'; + if (sscanf(uri, "%9[^:]://%99[^:]:%i/%198[^\n]", proto, ip, &port, page+1) == 4) { err = 0;} + else if (sscanf(uri, "%9[^:]://%99[^/]/%198[^\n]", proto, ip, page+1) == 3) { err = 0;} + else if (sscanf(uri, "%9[^:]://%99[^:]:%i[^\n]", proto, ip, &port) == 3) { err = 0;} + else if (sscanf(uri, "%9[^:]://%99[^\n]", proto, ip) == 2) { err = 0;} + + if (!err) + { +#ifndef IPV6 + struct sockaddr_in server; + + if((server.sin_addr.s_addr = inet_addr(ip)) == INADDR_NONE) + return -1; + + server.sin_port = htons(port); + server.sin_family = AF_INET; + + if((fd = socket(PF_INET, SOCK_STREAM, 6)) < 0) + { + return -1; + } + if(connect(fd, (struct sockaddr *)&server, sizeof(server))) + return -1; +#else + struct addrinfo hints; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ + hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */ + hints.ai_flags = 0; + hints.ai_protocol = 0; /* Any protocol */ + if (strncmp(proto,"http",4)) + { + LOG_ERROR("bad protocol type %s", proto); + return -1; + } + struct addrinfo *result, *rp; + char aport[6]; + sprintf(aport,"%u",port); + + err = getaddrinfo(ip, aport, &hints, &result); + if (err) + return err; + + for (rp = result; rp != NULL; rp = rp->ai_next) + { + int yes = 0; + + fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (fd == -1) + continue; + else if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0) + { + close(fd); + fd = -errno; + continue; + } + + if (connect(fd, rp->ai_addr, rp->ai_addrlen) != -1) + break; /* Success */ + + close(fd); + } + freeaddrinfo(result); + if (rp == NULL) + return -1; +#endif + + info->uri = uri; + http_get_transaction(fd, info); + + } + return fd; +} + +#ifdef HTTP_GET_MAIN +int main(int argc, char **argv) +{ + if (argc > 1); + { + struct http_info info; + int fd; + fd = http_get(argv[1], &info); + printf("content length = %u\n",info.length); + printf("content type = %s\n",info.mime); + if (fd > 0) + close(fd); + } + return 0; +} +#endif diff --git a/contribs/webclient.h b/contribs/webclient.h new file mode 100644 index 0000000..3830c7c --- /dev/null +++ b/contribs/webclient.h @@ -0,0 +1,13 @@ +#ifndef __NETWORK_HTTP_GET_H__ +#define __NETWORK_HTTP_GET_H__ +struct http_info +{ + const char *method; + const char *uri; + unsigned int length; + char mime[100]; +}; + +extern int http_get(char *uri, struct http_info *info); + +#endif diff --git a/src/Makefile.am b/src/Makefile.am index 6386016..e0ad73b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,4 +1,5 @@ bin_PROGRAMS = gmediarender +pkglib_LTLIBRARIES = gmrender_gst.la gmediarender_SOURCES = main.c git-version.h \ upnp_service.c upnp_control.c upnp_connmgr.c upnp_transport.c \ @@ -14,8 +15,8 @@ gmediarender_SOURCES = main.c git-version.h \ xmlescape.c xmlescape.h if HAVE_GST -gmediarender_SOURCES += \ - output_gstreamer.c output_gstreamer.h +gmrender_gst_la_SOURCES = \ + output_gstreamer.c endif main.c : git-version.h @@ -27,5 +28,17 @@ git-version.h: .FORCE .FORCE: +MOD_MAJOR_VERSION=$(word 1,$(subst ., ,$(GMRENDER_GST_VERSION))) +MOD_MINOR_VERSION=$(word 2,$(subst ., ,$(GMRENDER_GST_VERSION))) +MOD_MICRO_VERSION=$(word 3,$(subst ., ,$(GMRENDER_GST_VERSION))) + AM_CPPFLAGS = $(GLIB_CFLAGS) $(GST_CFLAGS) $(LIBUPNP_CFLAGS) -DPKG_DATADIR=\"$(datadir)/gmediarender\" -gmediarender_LDADD = $(GLIB_LIBS) $(GST_LIBS) $(LIBUPNP_LIBS) +gmediarender_CFLAGS = $(GLIB_CFLAGS) $(LIBUPNP_CFLAGS) -DPKG_DATADIR=\"$(datadir)/gmediarender\" -DLIBDIR=\"$(libdir)\" +gmediarender_LDADD = $(GLIB_LIBS) $(LIBUPNP_LIBS) -ldl +gmediarender_LDFLAGS = -rdynamic -Wl,-rpath -Wl,$(libdir) +gmrender_gst_la_CFLAGS = $(GLIB_CFLAGS) $(GST_CFLAGS) +gmrender_gst_la_CFLAGS += -DMOD_MAJOR_VERSION=$(MOD_MAJOR_VERSION) +gmrender_gst_la_CFLAGS += -DMOD_MINOR_VERSION=$(MOD_MINOR_VERSION) +gmrender_gst_la_CFLAGS += -DMOD_MICRO_VERSION=$(MOD_MICRO_VERSION) +gmrender_gst_la_LIBADD = $(GLIB_LIBS) $(GST_LIBS) +gmrender_gst_la_LDFLAGS = -module -release $(GMRENDER_GST_VERSION) diff --git a/src/main.c b/src/main.c index ef720e3..269e3b0 100644 --- a/src/main.c +++ b/src/main.c @@ -47,9 +47,6 @@ // For version strings of upnp and gstreamer #include -#ifdef HAVE_GST -# include -#endif #include "git-version.h" #include "logging.h" @@ -130,18 +127,10 @@ static GOptionEntry option_entries[] = { // Fill buffer with version information. Returns pointer to beginning of string. static const char *GetVersionInfo(char *buffer, size_t len) { -#ifdef HAVE_GST - snprintf(buffer, len, "gmediarender %s " - "(libupnp-%s; glib-%d.%d.%d; gstreamer-%d.%d.%d)", - GM_COMPILE_VERSION, UPNP_VERSION_STRING, - GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION, GLIB_MICRO_VERSION, - GST_VERSION_MAJOR, GST_VERSION_MINOR, GST_VERSION_MICRO); -#else snprintf(buffer, len, "gmediarender %s " - "(libupnp-%s; glib-%d.%d.%d; without gstreamer.)", + "(libupnp-%s; glib-%d.%d.%d)", GM_COMPILE_VERSION, UPNP_VERSION_STRING, GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION, GLIB_MICRO_VERSION); -#endif return buffer; } @@ -159,23 +148,15 @@ static void do_show_version(void) PACKAGE_STRING, version); } -static gboolean process_cmdline(int argc, char **argv) +static gboolean process_cmdline(int *argc, char **argv[]) { GOptionContext *ctx; GError *err = NULL; - int rc; ctx = g_option_context_new("- GMediaRender"); g_option_context_add_main_entries(ctx, option_entries, NULL); - rc = output_add_options(ctx); - if (rc != 0) { - fprintf(stderr, "Failed to add output options\n"); - g_option_context_free(ctx); - return FALSE; - } - - if (!g_option_context_parse (ctx, &argc, &argv, &err)) { + if (!g_option_context_parse (ctx, argc, argv, &err)) { fprintf(stderr, "Failed to initialize: %s\n", err->message); g_error_free (err); g_option_context_free(ctx); @@ -225,11 +206,7 @@ int main(int argc, char **argv) int rc; struct upnp_device_descriptor *upnp_renderer; -#if !GLIB_CHECK_VERSION(2,32,0) - g_thread_init (NULL); // Was necessary < glib 2.32, deprecated since. -#endif - - if (!process_cmdline(argc, argv)) { + if (!process_cmdline(&argc, &argv)) { return EXIT_FAILURE; } @@ -283,6 +260,9 @@ int main(int argc, char **argv) return EXIT_FAILURE; } + output_load_module(output); + output_add_options(&argc, &argv); + rc = output_init(output); if (rc != 0) { Log_error("main", diff --git a/src/output.c b/src/output.c index 6e30585..5b87ee9 100644 --- a/src/output.c +++ b/src/output.c @@ -32,64 +32,84 @@ #include #include #include +#include #include #include "logging.h" #include "output_module.h" -#ifdef HAVE_GST -#include "output_gstreamer.h" -#endif #include "output.h" -static struct output_module *modules[] = { -#ifdef HAVE_GST - &gstreamer_output, -#else - // this will be a runtime error, but there is not much point - // in waiting till then. -#error "No output configured. You need to ./configure --with-gstreamer" -#endif -}; +static struct output_module *modules = NULL; static struct output_module *output_module = NULL; -void output_dump_modules(void) +void output_append_module(struct output_module *new) { - int count; + if (new == NULL) + return; + new->next = modules; + modules = new; +} - count = sizeof(modules) / sizeof(struct output_module *); - if (count == 0) { +void output_load_module(const char *output) +{ + if (output != NULL) { + char *file = NULL; + if (asprintf(&file, LIBDIR"/gmediarender/gmrender_%s.so", output) > 0) { + void *dh = dlopen(file, RTLD_NOW | RTLD_DEEPBIND | RTLD_GLOBAL); + if (dh == NULL) { + Log_error("error", "ERROR: No such output library: '%s %s'", file, dlerror()); + } + free(file); + } + } +} + +void output_dump_modules(void) +{ + struct output_module *module = modules; + if (modules == NULL) puts(" NONE!"); - } else { - int i; - for (i=0; ishortname, - modules[i]->description, + modules->shortname, + modules->description, (i==0) ? " (default)" : ""); + if (module->version != NULL) + { + char buffer[64]; + printf("\tversion: %s\n", module->version(buffer, sizeof(buffer))); + } + i = 1; + module = module->next; } } } int output_init(const char *shortname) { - int count; - - count = sizeof(modules) / sizeof(struct output_module *); - if (count == 0) { + struct output_module *module = NULL; + module = modules; + if (module == NULL) { Log_error("output", "No output module available"); return -1; } + if (shortname == NULL) { - output_module = modules[0]; + output_module = module; } else { - int i; - for (i=0; ishortname, shortname)==0) { - output_module = modules[i]; + while (module != NULL) + { + if (strcmp(module->shortname, shortname)==0) { + output_module = module; break; } + module = module->next; } } @@ -130,18 +150,24 @@ int output_loop() return 0; } -int output_add_options(GOptionContext *ctx) +int output_add_options(int *argc, char **argv[]) { - int count, i; - - count = sizeof(modules) / sizeof(struct output_module *); - for (i = 0; i < count; ++i) { - if (modules[i]->add_options) { - int result = modules[i]->add_options(ctx); + struct output_module *module = modules; + if (*argc > 1 && !strcmp((*argv)[1], "--")) { + int i; + for (i = 1; i < *argc; i++) { + (*argv)[i] = (*argv)[i + 1]; + } + *argc = (*argc) - 1; + } + while (module != NULL) { + if (module->add_options) { + int result = module->add_options(argc, argv); if (result != 0) { return result; } } + module = module->next; } return 0; diff --git a/src/output.h b/src/output.h index 9230752..3c6e2b7 100644 --- a/src/output.h +++ b/src/output.h @@ -39,8 +39,12 @@ typedef void (*output_transition_cb_t)(enum PlayFeedback); // callback with changes we send back to the controlling layer. typedef void (*output_update_meta_cb_t)(const struct SongMetaData *); +struct output_module; +void output_append_module(struct output_module *new); +void output_load_module(const char *output); + int output_init(const char *shortname); -int output_add_options(GOptionContext *ctx); +int output_add_options(int *argc, char **argv[]); void output_dump_modules(void); int output_loop(void); diff --git a/src/output_gstreamer.c b/src/output_gstreamer.c index 871f8b5..05cf5a0 100644 --- a/src/output_gstreamer.c +++ b/src/output_gstreamer.c @@ -40,7 +40,8 @@ #include "logging.h" #include "upnp_connmgr.h" #include "output_module.h" -#include "output_gstreamer.h" + +void output_gstreamer_initlib(void) __attribute__((constructor)); static double buffer_duration = 0.0; /* Buffer disbled by default, see #182 */ @@ -194,7 +195,7 @@ static int output_gstreamer_pause(void) { } } -static int output_gstreamer_seek(gint64 position_nanos) { +static int output_gstreamer_seek(int64_t position_nanos) { if (gst_element_seek(player_, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET, position_nanos, @@ -401,22 +402,29 @@ static GOptionEntry option_entries[] = { }; -static int output_gstreamer_add_options(GOptionContext *ctx) +static int output_gstreamer_add_options(int *argc, char **argv[]) { - GOptionGroup *option_group; - option_group = g_option_group_new("gstout", "GStreamer Output Options", - "Show GStreamer Output Options", - NULL, NULL); - g_option_group_add_entries(option_group, option_entries); + GOptionContext *ctx; + GError *err = NULL; - g_option_context_add_group (ctx, option_group); + ctx = g_option_context_new("- GST module"); + g_option_context_add_main_entries(ctx, option_entries, NULL); g_option_context_add_group (ctx, gst_init_get_option_group ()); + + if (!g_option_context_parse (ctx, argc, argv, &err)) { + fprintf(stderr, "Failed to initialize: %s\n", err->message); + g_error_free (err); + g_option_context_free(ctx); + return FALSE; + } + + g_option_context_free(ctx); return 0; } -static int output_gstreamer_get_position(gint64 *track_duration, - gint64 *track_pos) { +static int output_gstreamer_get_position(int64_t *track_duration, + int64_t *track_pos) { *track_duration = last_known_time_.duration; *track_pos = last_known_time_.position; @@ -502,6 +510,8 @@ static int output_gstreamer_init(void) const char player_element_name[] = "playbin"; #endif + if (!gst_is_initialized()) + gst_init(NULL, NULL); player_ = gst_element_factory_make(player_element_name, "play"); assert(player_ != NULL); @@ -577,12 +587,22 @@ static int output_gstreamer_init(void) return 0; } -struct output_module gstreamer_output = { +static const char *output_gstreamer_version(char *buffer, size_t len) +{ + snprintf(buffer, len, "%d.%d.%d (glib-%d.%d.%d; gstreamer-%d.%d.%d)", + MOD_MAJOR_VERSION, MOD_MINOR_VERSION, MOD_MICRO_VERSION, + GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION, GLIB_MICRO_VERSION, + GST_VERSION_MAJOR, GST_VERSION_MINOR, GST_VERSION_MICRO); + return buffer; +} + +static struct output_module gstreamer_output = { .shortname = "gst", .description = "GStreamer multimedia framework", .add_options = output_gstreamer_add_options, .init = output_gstreamer_init, + .version = output_gstreamer_version, .set_uri = output_gstreamer_set_uri, .set_next_uri= output_gstreamer_set_next_uri, .play = output_gstreamer_play, @@ -595,4 +615,13 @@ struct output_module gstreamer_output = { .set_volume = output_gstreamer_set_volume, .get_mute = output_gstreamer_get_mute, .set_mute = output_gstreamer_set_mute, + .next = NULL, }; + +void output_gstreamer_initlib(void) +{ +#if !GLIB_CHECK_VERSION(2,32,0) + g_thread_init (NULL); // Was necessary < glib 2.32, deprecated since. +#endif + output_append_module(&gstreamer_output); +} diff --git a/src/output_module.h b/src/output_module.h index d3faa9d..51a8dc7 100644 --- a/src/output_module.h +++ b/src/output_module.h @@ -24,28 +24,32 @@ #ifndef _OUTPUT_MODULE_H #define _OUTPUT_MODULE_H +#include #include "output.h" struct output_module { const char *shortname; const char *description; - int (*add_options)(GOptionContext *ctx); + int (*add_options)(int *argc, char **argv[]); // Commands. int (*init)(void); + const char *(*version)(char *buffer, size_t len); void (*set_uri)(const char *uri, output_update_meta_cb_t meta_info); void (*set_next_uri)(const char *uri); int (*play)(output_transition_cb_t transition_callback); int (*stop)(void); int (*pause)(void); - int (*seek)(gint64 position_nanos); + int (*seek)(int64_t position_nanos); // parameters - int (*get_position)(gint64 *track_duration, gint64 *track_pos); + int (*get_position)(int64_t *track_duration, int64_t *track_pos); int (*get_volume)(float *); int (*set_volume)(float); int (*get_mute)(int *); int (*set_mute)(int); + + struct output_module *next; }; #endif