shout_plugin.c 12.5 KB
Newer Older
1 2 3
/*
 * Copyright (C) 2003-2009 The Music Player Daemon Project
 * http://www.musicpd.org
4 5 6 7 8 9 10 11 12 13
 *
 * 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.
14 15 16 17
 *
 * 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.
18 19
 */

20
#include "output_api.h"
21 22
#include "encoder_plugin.h"
#include "encoder_list.h"
Warren Dukes's avatar
Warren Dukes committed
23

24 25 26
#include <shout/shout.h>
#include <glib.h>

27
#include <assert.h>
28 29
#include <stdlib.h>
#include <stdio.h>
30

31 32 33
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "shout"

34
#define DEFAULT_CONN_TIMEOUT  2
35

36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
struct shout_buffer {
	unsigned char data[32768];
	size_t len;
};

struct shout_data {
	shout_t *shout_conn;
	shout_metadata_t *shout_meta;

	struct encoder *encoder;

	float quality;
	int bitrate;

	int timeout;

	struct shout_buffer buf;
};

Max Kellermann's avatar
Max Kellermann committed
55
static int shout_init_count;
56

57 58 59 60 61 62 63 64 65
/**
 * The quark used for GError.domain.
 */
static inline GQuark
shout_output_quark(void)
{
	return g_quark_from_static_string("shout_output");
}

66
static const struct encoder_plugin *
67
shout_encoder_plugin_get(const char *name)
68
{
69 70 71 72
	if (strcmp(name, "ogg") == 0)
		name = "vorbis";
	else if (strcmp(name, "mp3") == 0)
		name = "lame";
73

74
	return encoder_plugin_get(name);
75
}
76

Max Kellermann's avatar
Max Kellermann committed
77
static struct shout_data *new_shout_data(void)
Avuton Olrich's avatar
Avuton Olrich committed
78
{
79
	struct shout_data *ret = g_new(struct shout_data, 1);
80

Max Kellermann's avatar
Max Kellermann committed
81
	ret->shout_conn = shout_new();
82
	ret->shout_meta = shout_metadata_new();
83
	ret->bitrate = -1;
84
	ret->quality = -2.0;
85
	ret->timeout = DEFAULT_CONN_TIMEOUT;
86

87 88 89
	return ret;
}

Max Kellermann's avatar
Max Kellermann committed
90
static void free_shout_data(struct shout_data *sd)
Avuton Olrich's avatar
Avuton Olrich committed
91
{
92 93
	if (sd->shout_meta)
		shout_metadata_free(sd->shout_meta);
Max Kellermann's avatar
Max Kellermann committed
94 95
	if (sd->shout_conn)
		shout_free(sd->shout_conn);
96

97
	g_free(sd);
98 99
}

Max Kellermann's avatar
Max Kellermann committed
100 101 102
#define check_block_param(name) {		  \
		block_param = getBlockParam(param, name);	\
		if (!block_param) {					\
103 104
			g_error("no \"%s\" defined for shout device defined at line " \
				"%i\n", name, param->line);		\
Max Kellermann's avatar
Max Kellermann committed
105 106
		}							\
	}
107

108 109
static void *
my_shout_init_driver(const struct audio_format *audio_format,
110 111
		     const struct config_param *param,
		     GError **error)
Avuton Olrich's avatar
Avuton Olrich committed
112
{
Max Kellermann's avatar
Max Kellermann committed
113
	struct shout_data *sd;
Avuton Olrich's avatar
Avuton Olrich committed
114
	char *test;
115
	unsigned port;
Avuton Olrich's avatar
Avuton Olrich committed
116 117 118
	char *host;
	char *mount;
	char *passwd;
119
	const char *encoding;
120 121
	const struct encoder_plugin *encoder_plugin;
	unsigned shout_format;
122
	unsigned protocol;
Max Kellermann's avatar
Max Kellermann committed
123
	const char *user;
Avuton Olrich's avatar
Avuton Olrich committed
124
	char *name;
125
	const char *value;
126
	struct block_param *block_param;
127
	int public;
128

Max Kellermann's avatar
Max Kellermann committed
129
	sd = new_shout_data();
130

Max Kellermann's avatar
Max Kellermann committed
131
	if (shout_init_count == 0)
Avuton Olrich's avatar
Avuton Olrich committed
132
		shout_init();
133

Max Kellermann's avatar
Max Kellermann committed
134
	shout_init_count++;
135

Max Kellermann's avatar
Max Kellermann committed
136 137
	check_block_param("host");
	host = block_param->value;
138

Max Kellermann's avatar
Max Kellermann committed
139 140
	check_block_param("mount");
	mount = block_param->value;
141

142 143
	port = config_get_block_unsigned(param, "port", 0);
	if (port == 0) {
144 145 146
		g_set_error(error, shout_output_quark(), 0,
			    "shout port must be configured");
		return NULL;
147 148
	}

Max Kellermann's avatar
Max Kellermann committed
149 150
	check_block_param("password");
	passwd = block_param->value;
151

Max Kellermann's avatar
Max Kellermann committed
152 153
	check_block_param("name");
	name = block_param->value;
154

155
	public = config_get_block_bool(param, "public", false);
156

157
	user = config_get_block_string(param, "user", "source");
158

159 160 161
	value = config_get_block_string(param, "quality", NULL);
	if (value != NULL) {
		sd->quality = strtod(value, &test);
162

163
		if (*test != '\0' || sd->quality < -1.0 || sd->quality > 10.0) {
164 165 166 167 168
			g_set_error(error, shout_output_quark(), 0,
				    "shout quality \"%s\" is not a number in the "
				    "range -1 to 10, line %i",
				    value, param->line);
			return NULL;
169 170
		}

171
		if (config_get_block_string(param, "bitrate", NULL) != NULL) {
172 173 174 175
			g_set_error(error, shout_output_quark(), 0,
				    "quality and bitrate are "
				    "both defined");
			return NULL;
176
		}
Avuton Olrich's avatar
Avuton Olrich committed
177
	} else {
178
		value = config_get_block_string(param, "bitrate", NULL);
179 180 181 182 183
		if (value == NULL) {
			g_set_error(error, shout_output_quark(), 0,
				    "neither bitrate nor quality defined");
			return NULL;
		}
184

185
		sd->bitrate = strtol(value, &test, 10);
186

Avuton Olrich's avatar
Avuton Olrich committed
187
		if (*test != '\0' || sd->bitrate <= 0) {
188 189 190
			g_set_error(error, shout_output_quark(), 0,
				    "bitrate must be a positive integer");
			return NULL;
191
		}
192 193
	}

Max Kellermann's avatar
Max Kellermann committed
194
	check_block_param("format");
195

196
	encoding = config_get_block_string(param, "encoding", "ogg");
197
	encoder_plugin = shout_encoder_plugin_get(encoding);
198 199 200 201 202 203
	if (encoder_plugin == NULL) {
		g_set_error(error, shout_output_quark(), 0,
			    "couldn't find shout encoder plugin \"%s\"",
			    encoding);
		return NULL;
	}
204

205
	sd->encoder = encoder_init(encoder_plugin, param, error);
206
	if (sd->encoder == NULL)
207
		return NULL;
208 209 210 211 212 213

	if (strcmp(encoding, "mp3") == 0 || strcmp(encoding, "lame") == 0)
		shout_format = SHOUT_FORMAT_MP3;
	else
		shout_format = SHOUT_FORMAT_OGG;

214 215 216
	value = config_get_block_string(param, "protocol", NULL);
	if (value != NULL) {
		if (0 == strcmp(value, "shoutcast") &&
217 218 219 220 221 222
		    0 != strcmp(encoding, "mp3")) {
			g_set_error(error, shout_output_quark(), 0,
				    "you cannot stream \"%s\" to shoutcast, use mp3",
				    encoding);
			return NULL;
		} else if (0 == strcmp(value, "shoutcast"))
223
			protocol = SHOUT_PROTOCOL_ICY;
224
		else if (0 == strcmp(value, "icecast1"))
225
			protocol = SHOUT_PROTOCOL_XAUDIOCAST;
226
		else if (0 == strcmp(value, "icecast2"))
227
			protocol = SHOUT_PROTOCOL_HTTP;
228 229 230 231 232 233 234
		else {
			g_set_error(error, shout_output_quark(), 0,
				    "shout protocol \"%s\" is not \"shoutcast\" or "
				    "\"icecast1\"or \"icecast2\"",
				    value);
			return NULL;
		}
235 236 237 238
	} else {
		protocol = SHOUT_PROTOCOL_HTTP;
	}

Max Kellermann's avatar
Max Kellermann committed
239 240 241 242 243 244 245
	if (shout_set_host(sd->shout_conn, host) != SHOUTERR_SUCCESS ||
	    shout_set_port(sd->shout_conn, port) != SHOUTERR_SUCCESS ||
	    shout_set_password(sd->shout_conn, passwd) != SHOUTERR_SUCCESS ||
	    shout_set_mount(sd->shout_conn, mount) != SHOUTERR_SUCCESS ||
	    shout_set_name(sd->shout_conn, name) != SHOUTERR_SUCCESS ||
	    shout_set_user(sd->shout_conn, user) != SHOUTERR_SUCCESS ||
	    shout_set_public(sd->shout_conn, public) != SHOUTERR_SUCCESS ||
246
	    shout_set_format(sd->shout_conn, shout_format)
Avuton Olrich's avatar
Avuton Olrich committed
247
	    != SHOUTERR_SUCCESS ||
248
	    shout_set_protocol(sd->shout_conn, protocol) != SHOUTERR_SUCCESS ||
Max Kellermann's avatar
Max Kellermann committed
249
	    shout_set_agent(sd->shout_conn, "MPD") != SHOUTERR_SUCCESS) {
250 251 252
		g_set_error(error, shout_output_quark(), 0,
			    "%s", shout_get_error(sd->shout_conn));
		return NULL;
253
	}
254

255
	/* optional paramters */
256 257
	sd->timeout = config_get_block_unsigned(param, "timeout",
						DEFAULT_CONN_TIMEOUT);
258

259 260
	value = config_get_block_string(param, "genre", NULL);
	if (value != NULL && shout_set_genre(sd->shout_conn, value)) {
261 262 263
		g_set_error(error, shout_output_quark(), 0,
			    "%s", shout_get_error(sd->shout_conn));
		return NULL;
264 265
	}

266 267
	value = config_get_block_string(param, "description", NULL);
	if (value != NULL && shout_set_description(sd->shout_conn, value)) {
268 269 270
		g_set_error(error, shout_output_quark(), 0,
			    "%s", shout_get_error(sd->shout_conn));
		return NULL;
271 272
	}

273 274 275
	{
		char temp[11];
		memset(temp, 0, sizeof(temp));
Avuton Olrich's avatar
Avuton Olrich committed
276

277
		snprintf(temp, sizeof(temp), "%u", audio_format->channels);
Max Kellermann's avatar
Max Kellermann committed
278
		shout_set_audio_info(sd->shout_conn, SHOUT_AI_CHANNELS, temp);
279

280
		snprintf(temp, sizeof(temp), "%u", audio_format->sample_rate);
Avuton Olrich's avatar
Avuton Olrich committed
281

Max Kellermann's avatar
Max Kellermann committed
282
		shout_set_audio_info(sd->shout_conn, SHOUT_AI_SAMPLERATE, temp);
283

284
		if (sd->quality >= -1.0) {
285
			snprintf(temp, sizeof(temp), "%2.2f", sd->quality);
Max Kellermann's avatar
Max Kellermann committed
286
			shout_set_audio_info(sd->shout_conn, SHOUT_AI_QUALITY,
Avuton Olrich's avatar
Avuton Olrich committed
287 288
					     temp);
		} else {
289
			snprintf(temp, sizeof(temp), "%d", sd->bitrate);
Max Kellermann's avatar
Max Kellermann committed
290
			shout_set_audio_info(sd->shout_conn, SHOUT_AI_BITRATE,
Avuton Olrich's avatar
Avuton Olrich committed
291
					     temp);
292 293 294
		}
	}

295
	return sd;
296 297
}

298
static bool
299
handle_shout_error(struct shout_data *sd, int err, GError **error)
Avuton Olrich's avatar
Avuton Olrich committed
300 301
{
	switch (err) {
302 303
	case SHOUTERR_SUCCESS:
		break;
304

305 306
	case SHOUTERR_UNCONNECTED:
	case SHOUTERR_SOCKET:
307 308 309 310 311
		g_set_error(error, shout_output_quark(), err,
			    "Lost shout connection to %s:%i: %s",
			    shout_get_host(sd->shout_conn),
			    shout_get_port(sd->shout_conn),
			    shout_get_error(sd->shout_conn));
312 313
		return false;

314
	default:
315 316 317 318 319
		g_set_error(error, shout_output_quark(), err,
			    "connection to %s:%i error: %s",
			    shout_get_host(sd->shout_conn),
			    shout_get_port(sd->shout_conn),
			    shout_get_error(sd->shout_conn));
320
		return false;
321 322
	}

323
	return true;
324 325
}

326
static bool
327
write_page(struct shout_data *sd, GError **error)
Avuton Olrich's avatar
Avuton Olrich committed
328
{
329
	int err;
330

331 332 333 334
	assert(sd->encoder != NULL);

	sd->buf.len = encoder_read(sd->encoder,
				   sd->buf.data, sizeof(sd->buf.data));
335
	if (sd->buf.len == 0)
336
		return true;
337

Max Kellermann's avatar
Max Kellermann committed
338
	shout_sync(sd->shout_conn);
339
	err = shout_send(sd->shout_conn, sd->buf.data, sd->buf.len);
340
	if (!handle_shout_error(sd, err, error))
341
		return false;
342

343
	return true;
344 345
}

346
static void close_shout_conn(struct shout_data * sd)
Avuton Olrich's avatar
Avuton Olrich committed
347
{
348 349
	sd->buf.len = 0;

350 351
	if (sd->encoder != NULL) {
		if (encoder_flush(sd->encoder, NULL))
352
			write_page(sd, NULL);
353 354 355

		encoder_close(sd->encoder);
	}
356

Max Kellermann's avatar
Max Kellermann committed
357 358
	if (shout_get_connected(sd->shout_conn) != SHOUTERR_UNCONNECTED &&
	    shout_close(sd->shout_conn) != SHOUTERR_SUCCESS) {
359 360
		g_warning("problem closing connection to shout server: %s\n",
			  shout_get_error(sd->shout_conn));
361 362 363
	}
}

364
static void my_shout_finish_driver(void *data)
Avuton Olrich's avatar
Avuton Olrich committed
365
{
366
	struct shout_data *sd = (struct shout_data *)data;
367

368 369
	encoder_finish(sd->encoder);

Max Kellermann's avatar
Max Kellermann committed
370
	free_shout_data(sd);
371

Max Kellermann's avatar
Max Kellermann committed
372
	shout_init_count--;
373

Max Kellermann's avatar
Max Kellermann committed
374
	if (shout_init_count == 0)
Avuton Olrich's avatar
Avuton Olrich committed
375
		shout_shutdown();
376 377
}

378
static void my_shout_drop_buffered_audio(void *data)
Avuton Olrich's avatar
Avuton Olrich committed
379
{
380
	G_GNUC_UNUSED
381
	struct shout_data *sd = (struct shout_data *)data;
382 383

	/* needs to be implemented for shout */
384 385
}

386
static void my_shout_close_device(void *data)
Avuton Olrich's avatar
Avuton Olrich committed
387
{
388
	struct shout_data *sd = (struct shout_data *)data;
389

Max Kellermann's avatar
Max Kellermann committed
390
	close_shout_conn(sd);
391
}
392

393
static bool
394
shout_connect(struct shout_data *sd, GError **error)
Avuton Olrich's avatar
Avuton Olrich committed
395
{
396
	int state;
397

Max Kellermann's avatar
Max Kellermann committed
398
	state = shout_open(sd->shout_conn);
399
	switch (state) {
400 401
	case SHOUTERR_SUCCESS:
	case SHOUTERR_CONNECTED:
402 403
		return true;

404
	default:
405 406 407 408 409
		g_set_error(error, shout_output_quark(), 0,
			    "problem opening connection to shout server %s:%i: %s",
			    shout_get_host(sd->shout_conn),
			    shout_get_port(sd->shout_conn),
			    shout_get_error(sd->shout_conn));
410
		return false;
411
	}
412 413
}

414
static bool
415 416
my_shout_open_device(void *data, struct audio_format *audio_format,
		     GError **error)
417
{
418
	struct shout_data *sd = (struct shout_data *)data;
419
	bool ret;
420

421
	ret = shout_connect(sd, error);
422 423
	if (!ret)
		return false;
424

425 426
	sd->buf.len = 0;

427 428
	ret = encoder_open(sd->encoder, audio_format, error) &&
		write_page(sd, error);
429
	if (!ret) {
Max Kellermann's avatar
Max Kellermann committed
430
		shout_close(sd->shout_conn);
431
		return false;
432 433
	}

434
	return true;
435 436
}

437
static size_t
438
my_shout_play(void *data, const void *chunk, size_t size, GError **error)
Avuton Olrich's avatar
Avuton Olrich committed
439
{
440
	struct shout_data *sd = (struct shout_data *)data;
441

442 443 444 445
	return encoder_write(sd->encoder, chunk, size, error) &&
		write_page(sd, error)
		? size
		: 0;
446 447
}

448 449
static bool
my_shout_pause(void *data)
450 451 452
{
	static const char silence[1020];

453
	return my_shout_play(data, silence, sizeof(silence), NULL);
454 455
}

456 457 458 459 460 461 462 463 464
static void
shout_tag_to_metadata(const struct tag *tag, char *dest, size_t size)
{
	char artist[size];
	char title[size];

	artist[0] = 0;
	title[0] = 0;

Max Kellermann's avatar
Max Kellermann committed
465
	for (unsigned i = 0; i < tag->num_items; i++) {
466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481
		switch (tag->items[i]->type) {
		case TAG_ITEM_ARTIST:
			strncpy(artist, tag->items[i]->value, size);
			break;
		case TAG_ITEM_TITLE:
			strncpy(title, tag->items[i]->value, size);
			break;

		default:
			break;
		}
	}

	snprintf(dest, size, "%s - %s", title, artist);
}

482
static void my_shout_set_tag(void *data,
Max Kellermann's avatar
Max Kellermann committed
483
			     const struct tag *tag)
Avuton Olrich's avatar
Avuton Olrich committed
484
{
485
	struct shout_data *sd = (struct shout_data *)data;
486
	bool ret;
487 488 489 490 491 492 493 494 495 496 497 498
	GError *error = NULL;

	if (sd->encoder->plugin->tag != NULL) {
		/* encoder plugin supports stream tags */

		ret = encoder_flush(sd->encoder, &error);
		if (!ret) {
			g_warning("%s", error->message);
			g_error_free(error);
			return;
		}

499 500 501
		ret = write_page(sd, NULL);
		if (!ret)
			return;
502 503 504 505 506 507 508 509 510 511 512

		ret = encoder_tag(sd->encoder, tag, &error);
		if (!ret) {
			g_warning("%s", error->message);
			g_error_free(error);
		}
	} else {
		/* no stream tag support: fall back to icy-metadata */
		char song[1024];

		shout_tag_to_metadata(tag, song, sizeof(song));
513

514 515 516 517 518 519
		shout_metadata_add(sd->shout_meta, "song", song);
		if (SHOUTERR_SUCCESS != shout_set_metadata(sd->shout_conn,
							   sd->shout_meta)) {
			g_warning("error setting shout metadata\n");
		}
	}
520

521
	write_page(sd, NULL);
522 523
}

524
const struct audio_output_plugin shoutPlugin = {
525 526 527 528 529
	.name = "shout",
	.init = my_shout_init_driver,
	.finish = my_shout_finish_driver,
	.open = my_shout_open_device,
	.play = my_shout_play,
530
	.pause = my_shout_pause,
531 532 533
	.cancel = my_shout_drop_buffered_audio,
	.close = my_shout_close_device,
	.send_tag = my_shout_set_tag,
534
};