Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OpenHome services #45

Open
wants to merge 55 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
6a71712
move MIME type registration to a separate program module
Oct 19, 2013
044a132
add orginal copyright statement to reused code
Oct 19, 2013
1434cdd
initial implementation of OpenHome Playlist service
Oct 21, 2013
8c6f66c
allow NULL argument for output_gstreamer_set_next_uri
Oct 21, 2013
2441f7d
stop playback when current playlist item is deleted
Oct 21, 2013
3d0255c
move playlist handling utils to separate module
Oct 21, 2013
172f2a2
add utilities for sharing track metadata changes between modules
Oct 22, 2013
ad56432
add stream details callback to shared_metadata
Oct 23, 2013
193a4ad
make output modules provide shared metadata
Oct 23, 2013
52d5002
notify metadata listeners about stream details changes from gstreamer…
Oct 23, 2013
4c7366f
ooopps ...
Oct 23, 2013
6a7b202
initial implementation of OpenHome Info service
Oct 23, 2013
6c5a9f8
move time update thread out of upnp_transport module
Oct 23, 2013
454104a
initial implementation of OpenHone Time service
Oct 23, 2013
f2be8c3
stop notifications flood
Oct 23, 2013
7ed9369
initial implementation of OpenHome Product service
Oct 24, 2013
c147582
add option to select UPnP AV or OpenHome mode
Oct 24, 2013
0292047
include definition of memset()
Oct 25, 2013
5ef0cf4
improve state variable eventing
Oct 25, 2013
57ef9b6
OpenHome seems to use big-endian for IDs list
Oct 25, 2013
38c7822
working OpenHome renderer! (I hope)
Oct 25, 2013
afad10d
do not restart playback when next track is required, and there is non…
Oct 25, 2013
c5f129d
There are different namespaces for control and transport service
Oct 26, 2013
43f23f3
Qualify namespace choices
Oct 26, 2013
492d4a8
Include 'channel' attribute in volume-related event variables
Oct 26, 2013
a16663b
Add 'Loudness' to the set of attributes that need 'channel' qualifier
Oct 26, 2013
09dabd4
ignore non-eventable variable when building LastChange
Oct 26, 2013
119923e
do not send non-evened variables on initial sync
Oct 26, 2013
ea634d2
clean up evented variables
Oct 26, 2013
7255dae
moderate OpenHome playlist upodates
Oct 26, 2013
f4661bb
misc. state variables cleanup
Oct 26, 2013
97f7fd3
escape values sent in update events
Oct 26, 2013
ff84f5f
SourceXml variable is evented
Oct 26, 2013
70f29af
even if already playing, we must restart playback
Oct 27, 2013
865b4a5
start playback when SeekIndex action is executed
Oct 27, 2013
8a401e8
send all event notifications from dedicated thread
Oct 27, 2013
31a093e
add OpenHome Volume service
Oct 27, 2013
9304d20
return error if requested volume is out of range
Oct 27, 2013
e6ac317
store OpenHome renderer playlist in a file
Oct 28, 2013
46d85ca
fix compile warnings
Oct 28, 2013
d7e5d26
remove unused functions
Oct 28, 2013
32db236
report stream metadata changes in Metatext variable, not Metadata
Oct 29, 2013
cce2c8e
Add <upnp:class> tag to DIDL
Oct 29, 2013
d28cef7
There are some control points that set the 'next uri' to an
Oct 29, 2013
b2aca5b
Update README.md with the latest state of the gapless bug
Oct 29, 2013
24bc5cc
stop playback before changing track
Nov 13, 2013
4e99464
add TODO for VariableContainer_new() arguments
Nov 13, 2013
3319068
add TODO for resolving modules cross-dependecies
Nov 13, 2013
28ae891
clean up comments
Nov 13, 2013
a2fd33b
copy-paste is a BAD THING
Nov 13, 2013
b0537c4
pretect access to variable containers
Nov 22, 2013
e1269d6
primitive events moderation
Nov 22, 2013
fa65efd
in service descriptor, move var names to metadata
Nov 23, 2013
b2a0aa2
remove oudated TODO
Nov 23, 2013
ad75b67
more clear names for variable eventablity
Nov 28, 2013
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*~
*.o
*.in
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ gstreamer 0.10 up to 1.0.7. There was a [leaking thread bug][gst-bug-699794]
whose fix showed up in GStreamer 1.0.8.
If you are using an older version, then only restart of gmrender-resurrect
helps in these situations.
Unfortunately, in gstreamer 1.0.x version, there is another bug, a
[race condition][gst-bug-698750], that affects gapless playing with some
file formats (FLAC is ok, OGG has trouble), but apparantly, this is being
fixed in the development version of gstreamer (not verified yet).
There is a [bug][gst-bug-698750] before gstreamer 1.2 that affects gapless
playback.

Bottomline: use at least gstreamer 1.2.

If you want to use OpenMAX support, [there are reports][open-max-support], that
this works with a recent version of gstreamer 1.0.9.
this works with a recent version of gstreamer >= 1.0.9.

Installation
------------
Expand Down
11 changes: 10 additions & 1 deletion src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,16 @@ gmediarender_SOURCES = main.c git-version.h \
output.c output.h \
logging.h logging.c \
xmldoc.c xmldoc.h \
xmlescape.c xmlescape.h
xmlescape.c xmlescape.h \
mime_types.c mime_types.h \
oh_playlist.c oh_playlist.h \
oh_info.c oh_info.h \
oh_time.c oh_time.h \
oh_product.c oh_product.h \
oh_volume.c oh_volume.h \
oh_source.c oh_source.h \
playlist.c playlist.h \
shared_metadata.c shared_metadata.h

if HAVE_GST
gmediarender_SOURCES += \
Expand Down
44 changes: 36 additions & 8 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@
#include "upnp_device.h"
#include "upnp_renderer.h"
#include "upnp_transport.h"
#include "oh_playlist.h"
#include "oh_info.h"
#include "oh_time.h"
#include "oh_product.h"
#include "oh_volume.h"
#include "oh_source.h"

static gboolean show_version = FALSE;
static gboolean show_devicedesc = FALSE;
Expand All @@ -62,6 +68,9 @@ static gboolean show_control_scpd = FALSE;
static gboolean show_transport_scpd = FALSE;
static gboolean show_outputs = FALSE;
static gboolean daemon_mode = FALSE;
static gboolean openhome_mode = FALSE;

static gchar *playlist_file = NULL;

// IP-address seems strange in libupnp: they actually don't bind to
// that address, but to INADDR_ANY (miniserver.c in upnp library).
Expand All @@ -85,6 +94,10 @@ static const gchar *log_file = NULL;
static GOptionEntry option_entries[] = {
{ "version", 0, 0, G_OPTION_ARG_NONE, &show_version,
"Output version information and exit", NULL },
{ "openhome", 0, 0, G_OPTION_ARG_NONE, &openhome_mode,
"Device mode (upnpav or openhome, defaults to upnpav)", NULL },
{ "playlist", 0, 0, G_OPTION_ARG_STRING, &playlist_file,
"Filename to store playlist in OpenHome mode", NULL },
{ "ip-address", 'I', 0, G_OPTION_ARG_STRING, &ip_address,
"The local IP address the service is running and advertised "
"(only one, 0.0.0.0 won't work)", NULL },
Expand Down Expand Up @@ -190,7 +203,7 @@ static void init_logging(const char *log_file) {
int main(int argc, char **argv)
{
int rc;
struct upnp_device_descriptor *upnp_renderer;
struct upnp_device_descriptor *device_desc;

if (!process_cmdline(argc, argv)) {
return EXIT_FAILURE;
Expand Down Expand Up @@ -219,6 +232,7 @@ int main(int argc, char **argv)
}

init_logging(log_file);
variable_container_init();

// Now we're going to start threads etc, which means we need
// to become a daemon before that.
Expand All @@ -244,8 +258,11 @@ int main(int argc, char **argv)
}
#endif

upnp_renderer = upnp_renderer_descriptor(friendly_name, uuid);
if (upnp_renderer == NULL) {
if (!openhome_mode)
device_desc = upnp_renderer_descriptor(friendly_name, uuid);
else
device_desc = oh_source_descriptor(friendly_name, uuid);
if (device_desc == NULL) {
return EXIT_FAILURE;
}

Expand All @@ -256,6 +273,9 @@ int main(int argc, char **argv)
return EXIT_FAILURE;
}

if (openhome_mode && playlist_file != NULL)
oh_playlist_load(playlist_file);

struct upnp_device *device;
if (listen_port != 0 &&
(listen_port < 49152 || listen_port > 65535)) {
Expand All @@ -268,25 +288,33 @@ int main(int argc, char **argv)
listen_port);
return EXIT_FAILURE;
}
device = upnp_device_init(upnp_renderer, ip_address, listen_port);
device = upnp_device_init(device_desc, ip_address, listen_port);
if (device == NULL) {
Log_error("main", "ERROR: Failed to initialize UPnP device");
return EXIT_FAILURE;
}

upnp_transport_init(device);
upnp_control_init(device);
if (!openhome_mode) {
upnp_transport_init(device);
upnp_control_init(device);
} else {
oh_product_init(device);
oh_playlist_init(device);
oh_info_init(device);
oh_time_init(device);
oh_volume_init(device);
}

if (show_devicedesc) {
// This can only be run after all services have been
// initialized.
char *buf = upnp_create_device_desc(upnp_renderer);
char *buf = upnp_create_device_desc(device_desc);
assert(buf != NULL);
fputs(buf, stdout);
exit(EXIT_SUCCESS);
}

if (Log_info_enabled()) {
if (!openhome_mode && Log_info_enabled()) {
upnp_transport_register_variable_listener(log_variable_change,
(void*) "transport");
upnp_control_register_variable_listener(log_variable_change,
Expand Down
145 changes: 145 additions & 0 deletions src/mime_types.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/* mime_types.c - MIME types utils
*
* Copyright (C) 2005-2007 Ivo Clarysse
* Copyright (C) 2013 Andrey Demenev
*
* 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.
*
*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <stdio.h>

#include "mime_types.h"
#include "logging.h"

static struct mime_type *supported_types = NULL;

struct mime_type *get_supported_mime_types(void)
{
return supported_types;
}

static void register_mime_type_internal(const char *mime_type) {
struct mime_type *entry;

for (entry = supported_types; entry; entry = entry->next) {
if (strcmp(entry->mime_type, mime_type) == 0) {
return;
}
}
Log_info("mimetypes", "Registering support for '%s'", mime_type);

entry = malloc(sizeof(struct mime_type));
entry->mime_type = strdup(mime_type);
entry->next = supported_types;
supported_types = entry;
}

void register_mime_type(const char *mime_type) {
register_mime_type_internal(mime_type);
if (strcmp("audio/mpeg", mime_type) == 0) {
register_mime_type_internal("audio/x-mpeg");

// BubbleUPnP does not seem to match generic "audio/*" types,
// but only matches mime-types _exactly_, so we add some here.
// TODO(hzeller): we already add the "audio/*" mime-type
// output_gstream.c:scan_caps() which should just work once
// BubbleUPnP allows for matching "audio/*". Remove the code
// here.

// BubbleUPnP uses audio/x-scpl as an indicator to know if the
// renderer can handle it (otherwise it will proxy).
// Simple claim: if we can handle mpeg, then we can handle
// shoutcast.
// (For more accurate answer: we'd to check if all of
// mpeg, aac, aacp, ogg are supported).
register_mime_type_internal("audio/x-scpls");

// This is apparently something sent by the spotifyd
// https://gitorious.org/spotifyd
register_mime_type("audio/L16;rate=44100;channels=2");
}

// Some workaround: some controllers seem to match the version without
// x-, some with; though the mime-type is correct with x-, these formats
// seem to be common enough to sometimes be used without.
// If this works, we should probably collect all of these
// in a set emit always both, foo/bar and foo/x-bar, as it is a similar
// work-around as seen above with mpeg -> x-mpeg.
if (strcmp("audio/x-alac", mime_type) == 0) {
register_mime_type_internal("audio/alac");
}
if (strcmp("audio/x-aiff", mime_type) == 0) {
register_mime_type_internal("audio/aiff");
}
if (strcmp("audio/x-m4a", mime_type) == 0) {
register_mime_type_internal("audio/m4a");
register_mime_type_internal("audio/mp4");
}
}

char *get_mime_protocol_info(void)
{
struct mime_type *entry;
char *buf = NULL;
char *p;
int offset;
int bufsize = 0;
buf = malloc(bufsize);
p = buf;
assert(buf); // We assume an implementation that does 0-mallocs.
if (buf == NULL) {
fprintf(stderr, "%s: initial malloc failed\n",
__FUNCTION__);
return NULL;
}

for (entry = get_supported_mime_types(); entry; entry = entry->next) {
bufsize += strlen(entry->mime_type) + 1 + 8 + 3 + 2;
offset = p - buf;
buf = realloc(buf, bufsize);
if (buf == NULL) {
fprintf(stderr, "%s: realloc failed\n",
__FUNCTION__);
return NULL;
}
p = buf;
p += offset;
strncpy(p, "http-get:*:", 11);
p += 11;
strncpy(p, entry->mime_type, strlen(entry->mime_type));
p += strlen(entry->mime_type);
strncpy(p, ":*,", 3);
p += 3;
}
if (p > buf) {
p--;
*p = '\0';
}
*p = '\0';
return buf;
}

38 changes: 38 additions & 0 deletions src/mime_types.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/* mime_types.h - MIME types utils
*
* Copyright (C) 2005-2007 Ivo Clarysse
* Copyright (C) 2013 Andrey Demenev
*
* 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.
*
*/

#ifndef _MIME_TYPES_H
#define _MIME_TYPES_H

struct mime_type {
const char *mime_type;
struct mime_type *next;
};

struct mime_type *get_supported_mime_types(void);
void register_mime_type(const char *mime_type);
char *get_mime_protocol_info(void);

#endif

Loading