RecorderOutputPlugin.cxx 6.71 KB
Newer Older
1
/*
2
 * Copyright 2003-2018 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 "RecorderOutputPlugin.hxx"
21
#include "../OutputAPI.hxx"
22
#include "tag/Format.hxx"
23
#include "encoder/ToOutputStream.hxx"
24
#include "encoder/EncoderInterface.hxx"
25
#include "encoder/Configured.hxx"
26 27
#include "config/Domain.hxx"
#include "config/Path.hxx"
28
#include "Log.hxx"
29
#include "fs/AllocatedPath.hxx"
30
#include "fs/io/FileOutputStream.hxx"
31
#include "util/Domain.hxx"
32 33 34
#include "util/ScopeExit.hxx"

#include <stdexcept>
35
#include <memory>
36 37

#include <assert.h>
38 39 40
#include <stdlib.h>

static constexpr Domain recorder_domain("recorder");
41

42
class RecorderOutput final : AudioOutput {
43 44 45
	/**
	 * The configured encoder plugin.
	 */
46
	std::unique_ptr<PreparedEncoder> prepared_encoder;
47
	Encoder *encoder;
48 49 50 51

	/**
	 * The destination file name.
	 */
52
	AllocatedPath path = nullptr;
53

54 55 56 57 58 59 60 61 62 63 64 65
	/**
	 * 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;

66
	/**
67
	 * The destination file.
68
	 */
69
	FileOutputStream *file;
70

71
	RecorderOutput(const ConfigBlock &block);
72

73 74 75 76
public:
	static AudioOutput *Create(EventLoop &, const ConfigBlock &block) {
		return new RecorderOutput(block);
	}
77

78 79 80
private:
	void Open(AudioFormat &audio_format) override;
	void Close() noexcept override;
81

82 83 84
	/**
	 * Writes pending data from the encoder to the output file.
	 */
85
	void EncoderToFile();
86

87
	void SendTag(const Tag &tag) override;
88

89
	size_t Play(const void *chunk, size_t size) override;
90 91

private:
92
	gcc_pure
93
	bool HasDynamicPath() const noexcept {
94 95 96
		return !format_path.empty();
	}

97 98
	/**
	 * Finish the encoder and commit the file.
99 100
	 *
	 * Throws #std::runtime_error on error.
101
	 */
102
	void Commit();
103 104

	void FinishFormat();
105
	void ReopenFormat(AllocatedPath &&new_path);
106 107
};

108
RecorderOutput::RecorderOutput(const ConfigBlock &block)
109 110
	:AudioOutput(0),
	 prepared_encoder(CreateConfiguredEncoder(block))
111 112 113
{
	/* read configuration */

114
	path = block.GetPath("path");
115

116
	const char *fmt = block.GetBlockValue("format_path", nullptr);
117 118 119
	if (fmt != nullptr)
		format_path = fmt;

120 121
	if (path.IsNull() && fmt == nullptr)
		throw std::runtime_error("'path' not configured");
122

123 124
	if (!path.IsNull() && fmt != nullptr)
		throw std::runtime_error("Cannot have both 'path' and 'format_path'");
125 126
}

127 128
inline void
RecorderOutput::EncoderToFile()
129
{
130
	assert(file != nullptr);
131

132
	EncoderToOutputStream(*file, *encoder);
133 134
}

135
void
136
RecorderOutput::Open(AudioFormat &audio_format)
137 138 139
{
	/* create the output file */

140 141 142
	if (!HasDynamicPath()) {
		assert(!path.IsNull());

143
		file = new FileOutputStream(path);
144 145 146 147 148 149 150
	} 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;
	}
151 152 153

	/* open the encoder */

154 155
	try {
		encoder = prepared_encoder->Open(audio_format);
156
	} catch (...) {
157
		delete file;
158
		throw;
159 160
	}

161
	if (!HasDynamicPath()) {
162 163
		try {
			EncoderToFile();
164
		} catch (...) {
165
			delete encoder;
166
			throw;
167 168 169 170 171 172 173
		}
	} 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 */
174
		delete encoder;
175 176 177
	}
}

178 179
inline void
RecorderOutput::Commit()
180
{
181 182
	assert(!path.IsNull());

183 184
	/* flush the encoder and write the rest to the file */

185 186 187 188 189 190
	try {
		encoder->End();
		EncoderToFile();
	} catch (...) {
		delete encoder;
		throw;
191
	}
192 193 194

	/* now really close everything */

195
	delete encoder;
196

197 198 199 200 201
	try {
		file->Commit();
	} catch (...) {
		delete file;
		throw;
202
	}
203 204

	delete file;
205 206
}

207 208
void
RecorderOutput::Close() noexcept
209
{
210 211 212 213 214 215 216 217
	if (file == nullptr) {
		/* not currently encoding to a file; nothing needs to
		   be done now */
		assert(HasDynamicPath());
		assert(path.IsNull());
		return;
	}

218
	try {
219
		Commit();
220 221
	} catch (...) {
		LogError(std::current_exception());
222
	}
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237

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

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

	if (file == nullptr)
		return;

238
	try {
239
		Commit();
240 241
	} catch (...) {
		LogError(std::current_exception());
242
	}
243 244 245 246 247

	file = nullptr;
	path.SetNull();
}

248 249
inline void
RecorderOutput::ReopenFormat(AllocatedPath &&new_path)
250 251 252 253 254
{
	assert(HasDynamicPath());
	assert(path.IsNull());
	assert(file == nullptr);

255
	FileOutputStream *new_file = new FileOutputStream(new_path);
256 257

	AudioFormat new_audio_format = effective_audio_format;
258 259 260 261

	try {
		encoder = prepared_encoder->Open(new_audio_format);
	} catch (...) {
262
		delete new_file;
263
		throw;
264 265 266 267 268 269
	}

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

270 271
	try {
		EncoderToOutputStream(*new_file, *encoder);
272
	} catch (...) {
273
		delete encoder;
274
		delete new_file;
275
		throw;
276 277 278 279 280
	}

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

281 282
	FormatDebug(recorder_domain, "Recording to \"%s\"",
		    path.ToUTF8().c_str());
283 284
}

285
void
286 287
RecorderOutput::SendTag(const Tag &tag)
{
288 289 290 291 292 293 294 295 296 297
	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;
		}

298 299
		AtScopeExit(p) { free(p); };

300
		AllocatedPath new_path = nullptr;
301 302 303

		try {
			new_path = ParsePath(p);
304 305
		} catch (...) {
			LogError(std::current_exception());
306 307 308 309 310 311 312
			FinishFormat();
			return;
		}

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

313 314
			try {
				ReopenFormat(std::move(new_path));
315 316
			} catch (...) {
				LogError(std::current_exception());
317 318 319 320 321
				return;
			}
		}
	}

322 323 324
	encoder->PreTag();
	EncoderToFile();
	encoder->SendTag(tag);
325 326
}

327
size_t
328
RecorderOutput::Play(const void *chunk, size_t size)
329
{
330 331 332 333 334 335 336 337
	if (file == nullptr) {
		/* not currently encoding to a file; discard incoming
		   data */
		assert(HasDynamicPath());
		assert(path.IsNull());
		return size;
	}

338
	encoder->Write(chunk, size);
339

340
	EncoderToFile();
341 342

	return size;
343 344
}

345
const struct AudioOutputPlugin recorder_output_plugin = {
346 347
	"recorder",
	nullptr,
348
	&RecorderOutput::Create,
349
	nullptr,
350
};