RoarOutputPlugin.cxx 7.62 KB
Newer Older
1
/*
2
 * Copyright 2003-2016 The Music Player Daemon Project
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
 * Copyright (C) 2010-2011 Philipp 'ph3-der-loewe' Schafft
 * Copyright (C) 2010-2011 Hans-Kristian 'maister' Arntzen
 *
 * 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"
22
#include "RoarOutputPlugin.hxx"
23
#include "../OutputAPI.hxx"
24
#include "../Wrapper.hxx"
Max Kellermann's avatar
Max Kellermann committed
25
#include "mixer/MixerList.hxx"
26
#include "thread/Mutex.hxx"
27
#include "util/Domain.hxx"
28
#include "Log.hxx"
29

30
#include <string>
31
#include <stdexcept>
32

33 34 35
/* libroar/services.h declares roar_service_stream::new - work around
   this C++ problem */
#define new _new
36
#include <roaraudio.h>
37
#undef new
38

39
class RoarOutput {
40 41
	friend struct AudioOutputWrapper<RoarOutput>;

42
	AudioOutput base;
43

44
	const std::string host, name;
45

46
	roar_vs_t * vss;
47 48
	int err = ROAR_ERROR_NONE;
	const int role;
49 50
	struct roar_connection con;
	struct roar_audio_info info;
51
	mutable Mutex mutex;
52
	bool alive;
53

54
public:
55
	RoarOutput(const ConfigBlock &block);
56

57
	operator AudioOutput *() {
58 59 60
		return &base;
	}

61
	void Open(AudioFormat &audio_format);
62 63 64
	void Close();

	void SendTag(const Tag &tag);
65
	size_t Play(const void *chunk, size_t size);
66 67 68
	void Cancel();

	int GetVolume() const;
69
	void SetVolume(unsigned volume);
70
};
71

72
static constexpr Domain roar_output_domain("roar_output");
73

74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
gcc_pure
static int
GetConfiguredRole(const ConfigBlock &block)
{
	const char *role = block.GetBlockValue("role");
	return role != nullptr
		? roar_str2role(role)
		: ROAR_ROLE_MUSIC;
}

RoarOutput::RoarOutput(const ConfigBlock &block)
	:base(roar_output_plugin, block),
	 host(block.GetBlockValue("server", "")),
	 name(block.GetBlockValue("name", "MPD")),
	 role(GetConfiguredRole(block))
{
}

92 93
inline int
RoarOutput::GetVolume() const
94
{
95 96 97
	const ScopeLock protect(mutex);

	if (vss == nullptr || !alive)
98
		return -1;
99 100 101

	float l, r;
	int error;
102
	if (roar_vs_volume_get(vss, &l, &r, &error) < 0)
103
		throw std::runtime_error(roar_vs_strerr(error));
104

105 106 107
	return (l + r) * 50;
}

108
int
109
roar_output_get_volume(RoarOutput &roar)
110
{
111
	return roar.GetVolume();
112 113
}

114
inline void
115
RoarOutput::SetVolume(unsigned volume)
116 117 118
{
	assert(volume <= 100);

119 120
	const ScopeLock protect(mutex);
	if (vss == nullptr || !alive)
121
		throw std::runtime_error("closed");
122 123 124 125

	int error;
	float level = volume / 100.0;

126 127
	if (roar_vs_volume_mono(vss, level, &error) < 0)
		throw std::runtime_error(roar_vs_strerr(error));
128 129
}

130
void
131
roar_output_set_volume(RoarOutput &roar, unsigned volume)
132
{
133
	roar.SetVolume(volume);
134 135
}

136
static AudioOutput *
137
roar_init(const ConfigBlock &block)
138
{
139
	return *new RoarOutput(block);
140 141
}

142 143
static void
roar_use_audio_format(struct roar_audio_info *info,
144
		      AudioFormat &audio_format)
145
{
146 147
	info->rate = audio_format.sample_rate;
	info->channels = audio_format.channels;
148 149
	info->codec = ROAR_CODEC_PCM_S;

150 151 152 153
	switch (audio_format.format) {
	case SampleFormat::UNDEFINED:
	case SampleFormat::FLOAT:
	case SampleFormat::DSD:
154
		info->bits = 16;
155
		audio_format.format = SampleFormat::S16;
156 157
		break;

158
	case SampleFormat::S8:
159 160 161
		info->bits = 8;
		break;

162
	case SampleFormat::S16:
163 164 165
		info->bits = 16;
		break;

166
	case SampleFormat::S24_P32:
167
		info->bits = 32;
168
		audio_format.format = SampleFormat::S32;
169 170
		break;

171
	case SampleFormat::S32:
172 173 174 175 176
		info->bits = 32;
		break;
	}
}

177 178
inline void
RoarOutput::Open(AudioFormat &audio_format)
179
{
180
	const ScopeLock protect(mutex);
181

182 183
	if (roar_simple_connect(&con,
				host.empty() ? nullptr : host.c_str(),
184 185
				name.c_str()) < 0)
		throw std::runtime_error("Failed to connect to Roar server");
186

187
	vss = roar_vs_new_from_con(&con, &err);
188

189 190
	if (vss == nullptr || err != ROAR_ERROR_NONE)
		throw std::runtime_error("Failed to connect to server");
191

192
	roar_use_audio_format(&info, audio_format);
193

194 195
	if (roar_vs_stream(vss, &info, ROAR_DIR_PLAY, &err) < 0)
		throw std::runtime_error("Failed to start stream");
196

197 198
	roar_vs_role(vss, role, &err);
	alive = true;
199 200
}

201 202 203 204 205 206
inline void
RoarOutput::Close()
{
	const ScopeLock protect(mutex);

	alive = false;
207

208 209 210 211
	if (vss != nullptr)
		roar_vs_close(vss, ROAR_VS_TRUE, &err);
	vss = nullptr;
	roar_disconnect(&con);
212 213
}

214 215 216 217
inline void
RoarOutput::Cancel()
{
	const ScopeLock protect(mutex);
218

219
	if (vss == nullptr)
220 221
		return;

222 223 224 225 226 227 228 229 230 231 232
	roar_vs_t *_vss = vss;
	vss = nullptr;
	roar_vs_close(_vss, ROAR_VS_TRUE, &err);
	alive = false;

	_vss = roar_vs_new_from_con(&con, &err);
	if (_vss == nullptr)
		return;

	if (roar_vs_stream(_vss, &info, ROAR_DIR_PLAY, &err) < 0) {
		roar_vs_close(_vss, ROAR_VS_TRUE, &err);
233
		LogError(roar_output_domain, "Failed to start stream");
234 235 236
		return;
	}

237 238 239
	roar_vs_role(_vss, role, &err);
	vss = _vss;
	alive = true;
240 241
}

242
inline size_t
243
RoarOutput::Play(const void *chunk, size_t size)
244
{
245 246
	if (vss == nullptr)
		throw std::runtime_error("Connection is invalid");
247

248
	ssize_t nbytes = roar_vs_write(vss, chunk, size, &err);
249 250
	if (nbytes <= 0)
		throw std::runtime_error("Failed to play data");
251

252 253 254
	return nbytes;
}

255
static const char*
256
roar_tag_convert(TagType type, bool *is_uuid)
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
{
	*is_uuid = false;
	switch (type)
	{
		case TAG_ARTIST:
		case TAG_ALBUM_ARTIST:
			return "AUTHOR";
		case TAG_ALBUM:
			return "ALBUM";
		case TAG_TITLE:
			return "TITLE";
		case TAG_TRACK:
			return "TRACK";
		case TAG_NAME:
			return "NAME";
		case TAG_GENRE:
			return "GENRE";
		case TAG_DATE:
			return "DATE";
		case TAG_PERFORMER:
			return "PERFORMER";
		case TAG_COMMENT:
			return "COMMENT";
		case TAG_DISC:
			return "DISCID";
		case TAG_COMPOSER:
#ifdef ROAR_META_TYPE_COMPOSER
			return "COMPOSER";
#else
			return "AUTHOR";
#endif
		case TAG_MUSICBRAINZ_ARTISTID:
		case TAG_MUSICBRAINZ_ALBUMID:
		case TAG_MUSICBRAINZ_ALBUMARTISTID:
		case TAG_MUSICBRAINZ_TRACKID:
			*is_uuid = true;
			return "HASH";
294 295 296
		case TAG_MUSICBRAINZ_RELEASETRACKID:
			*is_uuid = true;
			return "HASH";
297 298

		default:
299
			return nullptr;
300 301 302
	}
}

303 304
inline void
RoarOutput::SendTag(const Tag &tag)
305
{
306
	if (vss == nullptr)
307 308
		return;

309
	const ScopeLock protect(mutex);
310

311
	size_t cnt = 0;
312 313 314 315
	struct roar_keyval vals[32];
	char uuid_buf[32][64];

	char timebuf[16];
316 317 318 319 320 321 322 323 324
	if (!tag.duration.IsNegative()) {
		const unsigned seconds = tag.duration.ToS();
		snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d",
			 seconds / 3600, (seconds % 3600) / 60, seconds % 60);

		vals[cnt].key = const_cast<char *>("LENGTH");
		vals[cnt].value = timebuf;
		++cnt;
	}
325

326 327 328 329
	for (const auto &item : tag) {
		if (cnt >= 32)
			break;

330
		bool is_uuid = false;
331
		const char *key = roar_tag_convert(item.type,
332 333
						   &is_uuid);
		if (key != nullptr) {
334 335
			vals[cnt].key = const_cast<char *>(key);

336
			if (is_uuid) {
337
				snprintf(uuid_buf[cnt], sizeof(uuid_buf[0]), "{UUID}%s",
338
					 item.value);
339
				vals[cnt].value = uuid_buf[cnt];
340
			} else {
341
				vals[cnt].value = const_cast<char *>(item.value);
342
			}
343

344 345 346 347
			cnt++;
		}
	}

348
	roar_vs_meta(vss, vals, cnt, &(err));
349 350
}

351
static void
352
roar_send_tag(AudioOutput *ao, const Tag &meta)
353 354
{
	RoarOutput *self = (RoarOutput *)ao;
355
	self->SendTag(meta);
356 357
}

358 359
typedef AudioOutputWrapper<RoarOutput> Wrapper;

360
const struct AudioOutputPlugin roar_output_plugin = {
361 362 363
	"roar",
	nullptr,
	roar_init,
364
	&Wrapper::Finish,
365 366
	nullptr,
	nullptr,
367 368
	&Wrapper::Open,
	&Wrapper::Close,
369 370
	nullptr,
	roar_send_tag,
371
	&Wrapper::Play,
372
	nullptr,
373
	&Wrapper::Cancel,
374 375
	nullptr,
	&roar_mixer_plugin,
376
};