RoarOutputPlugin.cxx 7.33 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2017 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"
Max Kellermann's avatar
Max Kellermann committed
24
#include "mixer/MixerList.hxx"
25
#include "thread/Mutex.hxx"
26
#include "util/Domain.hxx"
27
#include "Log.hxx"
28

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

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

38 39
#include <assert.h>

40
class RoarOutput final : AudioOutput {
41
	const std::string host, name;
42

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

51
public:
52
	RoarOutput(const ConfigBlock &block);
53

54
	static AudioOutput *Create(EventLoop &, const ConfigBlock &block) {
55 56 57
		return new RoarOutput(block);
	}

58
	int GetVolume() const;
59
	void SetVolume(unsigned volume);
60 61 62 63 64 65 66 67

private:
	void Open(AudioFormat &audio_format) override;
	void Close() noexcept override;

	void SendTag(const Tag &tag) override;
	size_t Play(const void *chunk, size_t size) override;
	void Cancel() noexcept override;
68
};
69

70
static constexpr Domain roar_output_domain("roar_output");
71

72 73
gcc_pure
static int
74
GetConfiguredRole(const ConfigBlock &block) noexcept
75 76 77 78 79 80 81 82
{
	const char *role = block.GetBlockValue("role");
	return role != nullptr
		? roar_str2role(role)
		: ROAR_ROLE_MUSIC;
}

RoarOutput::RoarOutput(const ConfigBlock &block)
83
	:AudioOutput(0),
84 85 86 87 88 89
	 host(block.GetBlockValue("server", "")),
	 name(block.GetBlockValue("name", "MPD")),
	 role(GetConfiguredRole(block))
{
}

90 91
inline int
RoarOutput::GetVolume() const
92
{
93
	const std::lock_guard<Mutex> protect(mutex);
94 95

	if (vss == nullptr || !alive)
96
		return -1;
97 98 99

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

103 104 105
	return (l + r) * 50;
}

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

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

117
	const std::lock_guard<Mutex> protect(mutex);
118
	if (vss == nullptr || !alive)
119
		throw std::runtime_error("closed");
120 121 122 123

	int error;
	float level = volume / 100.0;

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

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

134 135
static void
roar_use_audio_format(struct roar_audio_info *info,
136
		      AudioFormat &audio_format)
137
{
138 139
	info->rate = audio_format.sample_rate;
	info->channels = audio_format.channels;
140 141
	info->codec = ROAR_CODEC_PCM_S;

142 143 144 145
	switch (audio_format.format) {
	case SampleFormat::UNDEFINED:
	case SampleFormat::FLOAT:
	case SampleFormat::DSD:
146
		info->bits = 16;
147
		audio_format.format = SampleFormat::S16;
148 149
		break;

150
	case SampleFormat::S8:
151 152 153
		info->bits = 8;
		break;

154
	case SampleFormat::S16:
155 156 157
		info->bits = 16;
		break;

158
	case SampleFormat::S24_P32:
159
		info->bits = 32;
160
		audio_format.format = SampleFormat::S32;
161 162
		break;

163
	case SampleFormat::S32:
164 165 166 167 168
		info->bits = 32;
		break;
	}
}

169
void
170
RoarOutput::Open(AudioFormat &audio_format)
171
{
172
	const std::lock_guard<Mutex> protect(mutex);
173

174 175
	if (roar_simple_connect(&con,
				host.empty() ? nullptr : host.c_str(),
176 177
				name.c_str()) < 0)
		throw std::runtime_error("Failed to connect to Roar server");
178

179
	vss = roar_vs_new_from_con(&con, &err);
180

181 182
	if (vss == nullptr || err != ROAR_ERROR_NONE)
		throw std::runtime_error("Failed to connect to server");
183

184
	roar_use_audio_format(&info, audio_format);
185

186 187
	if (roar_vs_stream(vss, &info, ROAR_DIR_PLAY, &err) < 0)
		throw std::runtime_error("Failed to start stream");
188

189 190
	roar_vs_role(vss, role, &err);
	alive = true;
191 192
}

193 194
void
RoarOutput::Close() noexcept
195
{
196
	const std::lock_guard<Mutex> protect(mutex);
197 198

	alive = false;
199

200 201 202 203
	if (vss != nullptr)
		roar_vs_close(vss, ROAR_VS_TRUE, &err);
	vss = nullptr;
	roar_disconnect(&con);
204 205
}

206 207
void
RoarOutput::Cancel() noexcept
208
{
209
	const std::lock_guard<Mutex> protect(mutex);
210

211
	if (vss == nullptr)
212 213
		return;

214 215 216 217 218 219 220 221 222 223 224
	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);
225
		LogError(roar_output_domain, "Failed to start stream");
226 227 228
		return;
	}

229 230 231
	roar_vs_role(_vss, role, &err);
	vss = _vss;
	alive = true;
232 233
}

234
size_t
235
RoarOutput::Play(const void *chunk, size_t size)
236
{
237 238
	if (vss == nullptr)
		throw std::runtime_error("Connection is invalid");
239

240
	ssize_t nbytes = roar_vs_write(vss, chunk, size, &err);
241 242
	if (nbytes <= 0)
		throw std::runtime_error("Failed to play data");
243

244 245 246
	return nbytes;
}

247
static const char*
248
roar_tag_convert(TagType type, bool *is_uuid)
249 250 251 252 253 254 255 256 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
{
	*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:
284
		case TAG_MUSICBRAINZ_RELEASETRACKID:
285
		case TAG_MUSICBRAINZ_WORKID:
286 287
			*is_uuid = true;
			return "HASH";
288 289

		default:
290
			return nullptr;
291 292 293
	}
}

294
void
295
RoarOutput::SendTag(const Tag &tag)
296
{
297
	if (vss == nullptr)
298 299
		return;

300
	const std::lock_guard<Mutex> protect(mutex);
301

302
	size_t cnt = 0;
303 304 305 306
	struct roar_keyval vals[32];
	char uuid_buf[32][64];

	char timebuf[16];
307 308 309 310 311 312 313 314 315
	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;
	}
316

317 318 319 320
	for (const auto &item : tag) {
		if (cnt >= 32)
			break;

321
		bool is_uuid = false;
322
		const char *key = roar_tag_convert(item.type,
323 324
						   &is_uuid);
		if (key != nullptr) {
325 326
			vals[cnt].key = const_cast<char *>(key);

327
			if (is_uuid) {
328
				snprintf(uuid_buf[cnt], sizeof(uuid_buf[0]), "{UUID}%s",
329
					 item.value);
330
				vals[cnt].value = uuid_buf[cnt];
331
			} else {
332
				vals[cnt].value = const_cast<char *>(item.value);
333
			}
334

335 336 337 338
			cnt++;
		}
	}

339
	roar_vs_meta(vss, vals, cnt, &(err));
340 341
}

342
const struct AudioOutputPlugin roar_output_plugin = {
343 344
	"roar",
	nullptr,
345
	&RoarOutput::Create,
346
	&roar_mixer_plugin,
347
};