RecorderOutputPlugin.cxx 7.47 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
 * http://www.musicpd.org
 *
 * 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.
 */

20
#include "config.h"
21
#include "RecorderOutputPlugin.hxx"
22
#include "../OutputAPI.hxx"
23
#include "../Wrapper.hxx"
24
#include "tag/Format.hxx"
25
#include "encoder/ToOutputStream.hxx"
26
#include "encoder/EncoderInterface.hxx"
27 28
#include "encoder/EncoderPlugin.hxx"
#include "encoder/EncoderList.hxx"
29
#include "config/ConfigError.hxx"
30
#include "config/ConfigPath.hxx"
31
#include "Log.hxx"
32
#include "fs/AllocatedPath.hxx"
33
#include "fs/io/FileOutputStream.hxx"
34
#include "util/RuntimeError.hxx"
35
#include "util/Domain.hxx"
36 37 38
#include "util/ScopeExit.hxx"

#include <stdexcept>
39 40

#include <assert.h>
41 42 43
#include <stdlib.h>

static constexpr Domain recorder_domain("recorder");
44

45 46 47
class RecorderOutput {
	friend struct AudioOutputWrapper<RecorderOutput>;

48
	AudioOutput base;
49

50 51 52
	/**
	 * The configured encoder plugin.
	 */
53 54
	PreparedEncoder *prepared_encoder = nullptr;
	Encoder *encoder;
55 56 57 58

	/**
	 * The destination file name.
	 */
59
	AllocatedPath path = AllocatedPath::Null();
60

61 62 63 64 65 66 67 68 69 70 71 72
	/**
	 * A string that will be used with FormatTag() to build the
	 * destination path.
	 */
	std::string format_path;

	/**
	 * The #AudioFormat that is currently active.  This is used
	 * for switching to another file.
	 */
	AudioFormat effective_audio_format;

73
	/**
74
	 * The destination file.
75
	 */
76
	FileOutputStream *file;
77

78
	RecorderOutput(const ConfigBlock &block);
79

80
	~RecorderOutput() {
81
		delete prepared_encoder;
82 83
	}

84
	static RecorderOutput *Create(const ConfigBlock &block);
85

86
	void Open(AudioFormat &audio_format);
87 88
	void Close();

89 90 91
	/**
	 * Writes pending data from the encoder to the output file.
	 */
92
	void EncoderToFile();
93 94

	void SendTag(const Tag &tag);
95

96
	size_t Play(const void *chunk, size_t size);
97 98

private:
99 100 101 102 103
	gcc_pure
	bool HasDynamicPath() const {
		return !format_path.empty();
	}

104 105
	/**
	 * Finish the encoder and commit the file.
106 107
	 *
	 * Throws #std::runtime_error on error.
108
	 */
109
	void Commit();
110 111

	void FinishFormat();
112
	void ReopenFormat(AllocatedPath &&new_path);
113 114
};

115 116
RecorderOutput::RecorderOutput(const ConfigBlock &block)
	:base(recorder_output_plugin, block)
117 118 119
{
	/* read configuration */

120
	const char *encoder_name =
121
		block.GetBlockValue("encoder", "vorbis");
122
	const auto encoder_plugin = encoder_plugin_get(encoder_name);
123 124
	if (encoder_plugin == nullptr)
		throw FormatRuntimeError("No such encoder: %s", encoder_name);
125

126
	path = block.GetPath("path");
127

128
	const char *fmt = block.GetBlockValue("format_path", nullptr);
129 130 131
	if (fmt != nullptr)
		format_path = fmt;

132 133
	if (path.IsNull() && fmt == nullptr)
		throw std::runtime_error("'path' not configured");
134

135 136
	if (!path.IsNull() && fmt != nullptr)
		throw std::runtime_error("Cannot have both 'path' and 'format_path'");
137 138 139

	/* initialize encoder */

140
	prepared_encoder = encoder_init(*encoder_plugin, block);
141 142
}

143
RecorderOutput *
144
RecorderOutput::Create(const ConfigBlock &block)
145
{
146
	return new RecorderOutput(block);
147 148
}

149 150
inline void
RecorderOutput::EncoderToFile()
151
{
152
	assert(file != nullptr);
153

154
	EncoderToOutputStream(*file, *encoder);
155 156
}

157 158
inline void
RecorderOutput::Open(AudioFormat &audio_format)
159 160 161
{
	/* create the output file */

162 163 164
	if (!HasDynamicPath()) {
		assert(!path.IsNull());

165
		file = new FileOutputStream(path);
166 167 168 169 170 171 172
	} else {
		/* don't open the file just yet; wait until we have
		   a tag that we can use to build the path */
		assert(path.IsNull());

		file = nullptr;
	}
173 174 175

	/* open the encoder */

176 177 178
	try {
		encoder = prepared_encoder->Open(audio_format);
	} catch (const std::runtime_error &) {
179
		delete file;
180
		throw;
181 182
	}

183
	if (!HasDynamicPath()) {
184 185
		try {
			EncoderToFile();
186
		} catch (const std::runtime_error &) {
187
			delete encoder;
188
			throw;
189 190 191 192 193 194 195
		}
	} else {
		/* remember the AudioFormat for ReopenFormat() */
		effective_audio_format = audio_format;

		/* close the encoder for now; it will be opened as
		   soon as we have received a tag */
196
		delete encoder;
197 198 199
	}
}

200 201
inline void
RecorderOutput::Commit()
202
{
203 204
	assert(!path.IsNull());

205 206
	/* flush the encoder and write the rest to the file */

207 208 209 210 211 212
	try {
		encoder->End();
		EncoderToFile();
	} catch (...) {
		delete encoder;
		throw;
213
	}
214 215 216

	/* now really close everything */

217
	delete encoder;
218

219 220 221 222 223
	try {
		file->Commit();
	} catch (...) {
		delete file;
		throw;
224
	}
225 226

	delete file;
227 228 229 230 231
}

inline void
RecorderOutput::Close()
{
232 233 234 235 236 237 238 239
	if (file == nullptr) {
		/* not currently encoding to a file; nothing needs to
		   be done now */
		assert(HasDynamicPath());
		assert(path.IsNull());
		return;
	}

240
	try {
241
		Commit();
242 243 244
	} catch (const std::exception &e) {
		LogError(e);
	}
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259

	if (HasDynamicPath()) {
		assert(!path.IsNull());
		path.SetNull();
	}
}

void
RecorderOutput::FinishFormat()
{
	assert(HasDynamicPath());

	if (file == nullptr)
		return;

260
	try {
261
		Commit();
262 263 264
	} catch (const std::exception &e) {
		LogError(e);
	}
265 266 267 268 269

	file = nullptr;
	path.SetNull();
}

270 271
inline void
RecorderOutput::ReopenFormat(AllocatedPath &&new_path)
272 273 274 275 276
{
	assert(HasDynamicPath());
	assert(path.IsNull());
	assert(file == nullptr);

277
	FileOutputStream *new_file = new FileOutputStream(path);
278 279

	AudioFormat new_audio_format = effective_audio_format;
280 281 282 283

	try {
		encoder = prepared_encoder->Open(new_audio_format);
	} catch (...) {
284
		delete new_file;
285
		throw;
286 287 288 289 290 291
	}

	/* reopening the encoder must always result in the same
	   AudioFormat as before */
	assert(new_audio_format == effective_audio_format);

292 293 294
	try {
		EncoderToOutputStream(*new_file, *encoder);
	} catch (const std::exception &e) {
295
		delete encoder;
296
		delete new_file;
297
		throw;
298 299 300 301 302
	}

	path = std::move(new_path);
	file = new_file;

303 304
	FormatDebug(recorder_domain, "Recording to \"%s\"",
		    path.ToUTF8().c_str());
305 306
}

307 308 309
inline void
RecorderOutput::SendTag(const Tag &tag)
{
310 311 312 313 314 315 316 317 318 319
	if (HasDynamicPath()) {
		char *p = FormatTag(tag, format_path.c_str());
		if (p == nullptr || *p == 0) {
			/* no path could be composed with this tag:
			   don't write a file */
			free(p);
			FinishFormat();
			return;
		}

320 321 322 323 324 325 326 327
		AtScopeExit(p) { free(p); };

		AllocatedPath new_path = AllocatedPath::Null();

		try {
			new_path = ParsePath(p);
		} catch (const std::runtime_error &e) {
			LogError(e);
328 329 330 331 332 333 334
			FinishFormat();
			return;
		}

		if (new_path != path) {
			FinishFormat();

335 336 337 338
			try {
				ReopenFormat(std::move(new_path));
			} catch (const std::runtime_error &e) {
				LogError(e);
339 340 341 342 343
				return;
			}
		}
	}

344 345 346
	encoder->PreTag();
	EncoderToFile();
	encoder->SendTag(tag);
347 348
}

349
inline size_t
350
RecorderOutput::Play(const void *chunk, size_t size)
351
{
352 353 354 355 356 357 358 359
	if (file == nullptr) {
		/* not currently encoding to a file; discard incoming
		   data */
		assert(HasDynamicPath());
		assert(path.IsNull());
		return size;
	}

360
	encoder->Write(chunk, size);
361

362
	EncoderToFile();
363 364

	return size;
365 366
}

367 368
typedef AudioOutputWrapper<RecorderOutput> Wrapper;

369
const struct AudioOutputPlugin recorder_output_plugin = {
370 371
	"recorder",
	nullptr,
372
	&Wrapper::Init,
373
	&Wrapper::Finish,
374 375
	nullptr,
	nullptr,
376 377
	&Wrapper::Open,
	&Wrapper::Close,
378
	nullptr,
379
	&Wrapper::SendTag,
380
	&Wrapper::Play,
381 382 383 384
	nullptr,
	nullptr,
	nullptr,
	nullptr,
385
};