VorbisEncoderPlugin.cxx 8.69 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright (C) 2003-2015 The Music Player Daemon Project
3 4 5 6 7 8 9 10 11 12 13
 * 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.
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 "config.h"
21
#include "VorbisEncoderPlugin.hxx"
22
#include "OggStream.hxx"
23
#include "OggSerial.hxx"
24
#include "../EncoderAPI.hxx"
25
#include "tag/Tag.hxx"
26
#include "AudioFormat.hxx"
27
#include "config/ConfigError.hxx"
28
#include "util/StringUtil.hxx"
29
#include "util/NumberParser.hxx"
30 31
#include "util/Error.hxx"
#include "util/Domain.hxx"
32 33 34 35 36

#include <vorbis/vorbisenc.h>

struct vorbis_encoder {
	/** the base class */
37
	Encoder encoder;
38 39 40 41 42 43 44 45

	/* configuration */

	float quality;
	int bitrate;

	/* runtime information */

46
	AudioFormat audio_format;
47 48 49 50

	vorbis_dsp_state vd;
	vorbis_block vb;
	vorbis_info vi;
51

52
	OggStream stream;
53

54
	vorbis_encoder():encoder(vorbis_encoder_plugin) {}
55 56
};

57
static constexpr Domain vorbis_encoder_domain("vorbis_encoder");
58 59

static bool
60
vorbis_encoder_configure(struct vorbis_encoder &encoder,
61
			 const ConfigBlock &block, Error &error)
62
{
63
	const char *value = block.GetBlockValue("quality");
64
	if (value != nullptr) {
65 66
		/* a quality was configured (VBR) */

67
		char *endptr;
68
		encoder.quality = ParseDouble(value, &endptr);
69

70 71
		if (*endptr != '\0' || encoder.quality < -1.0 ||
		    encoder.quality > 10.0) {
72 73
			error.Format(config_domain,
				     "quality \"%s\" is not a number in the "
74 75
				     "range -1 to 10",
				     value);
76 77 78
			return false;
		}

79
		if (block.GetBlockValue("bitrate") != nullptr) {
80 81
			error.Set(config_domain,
				  "quality and bitrate are both defined");
82 83 84 85 86
			return false;
		}
	} else {
		/* a bit rate was configured */

87
		value = block.GetBlockValue("bitrate");
88
		if (value == nullptr) {
89 90
			error.Set(config_domain,
				  "neither bitrate nor quality defined");
91 92 93
			return false;
		}

94
		encoder.quality = -2.0;
95

96
		char *endptr;
97 98
		encoder.bitrate = ParseInt(value, &endptr);
		if (*endptr != '\0' || encoder.bitrate <= 0) {
99 100
			error.Set(config_domain,
				  "bitrate should be a positive integer");
101 102 103 104 105 106 107
			return false;
		}
	}

	return true;
}

108
static Encoder *
109
vorbis_encoder_init(const ConfigBlock &block, Error &error)
110
{
111
	vorbis_encoder *encoder = new vorbis_encoder();
112

113 114
	/* load configuration from "block" */
	if (!vorbis_encoder_configure(*encoder, block, error)) {
115
		/* configuration has failed, roll back and return error */
116
		delete encoder;
117
		return nullptr;
118 119 120 121 122 123
	}

	return &encoder->encoder;
}

static void
124
vorbis_encoder_finish(Encoder *_encoder)
125 126 127 128 129
{
	struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder;

	/* the real libvorbis/libogg cleanup was already performed by
	   vorbis_encoder_close(), so no real work here */
130
	delete encoder;
131 132 133
}

static bool
134
vorbis_encoder_reinit(struct vorbis_encoder &encoder, Error &error)
135
{
136
	vorbis_info_init(&encoder.vi);
137

138
	if (encoder.quality >= -1.0) {
139 140
		/* a quality was configured (VBR) */

141 142 143 144
		if (0 != vorbis_encode_init_vbr(&encoder.vi,
						encoder.audio_format.channels,
						encoder.audio_format.sample_rate,
						encoder.quality * 0.1)) {
145 146
			error.Set(vorbis_encoder_domain,
				  "error initializing vorbis vbr");
147
			vorbis_info_clear(&encoder.vi);
148 149 150 151 152
			return false;
		}
	} else {
		/* a bit rate was configured */

153 154 155 156
		if (0 != vorbis_encode_init(&encoder.vi,
					    encoder.audio_format.channels,
					    encoder.audio_format.sample_rate, -1.0,
					    encoder.bitrate * 1000, -1.0)) {
157 158
			error.Set(vorbis_encoder_domain,
				  "error initializing vorbis encoder");
159
			vorbis_info_clear(&encoder.vi);
160 161 162 163
			return false;
		}
	}

164 165 166
	vorbis_analysis_init(&encoder.vd, &encoder.vi);
	vorbis_block_init(&encoder.vd, &encoder.vb);
	encoder.stream.Initialize(GenerateOggSerial());
167 168 169 170 171

	return true;
}

static void
172
vorbis_encoder_headerout(struct vorbis_encoder &encoder, vorbis_comment &vc)
173 174 175
{
	ogg_packet packet, comments, codebooks;

176
	vorbis_analysis_headerout(&encoder.vd, &vc,
177 178
				  &packet, &comments, &codebooks);

179 180 181
	encoder.stream.PacketIn(packet);
	encoder.stream.PacketIn(comments);
	encoder.stream.PacketIn(codebooks);
182
}
183

184
static void
185
vorbis_encoder_send_header(struct vorbis_encoder &encoder)
186 187 188 189
{
	vorbis_comment vc;

	vorbis_comment_init(&vc);
190
	vorbis_encoder_headerout(encoder, vc);
191
	vorbis_comment_clear(&vc);
192 193 194
}

static bool
195
vorbis_encoder_open(Encoder *_encoder,
196
		    AudioFormat &audio_format,
197
		    Error &error)
198
{
199
	struct vorbis_encoder &encoder = *(struct vorbis_encoder *)_encoder;
200

201
	audio_format.format = SampleFormat::FLOAT;
202

203
	encoder.audio_format = audio_format;
204

205
	if (!vorbis_encoder_reinit(encoder, error))
206 207 208
		return false;

	vorbis_encoder_send_header(encoder);
209

210 211 212
	return true;
}

213
static void
214
vorbis_encoder_clear(struct vorbis_encoder &encoder)
215
{
216 217 218 219
	encoder.stream.Deinitialize();
	vorbis_block_clear(&encoder.vb);
	vorbis_dsp_clear(&encoder.vd);
	vorbis_info_clear(&encoder.vi);
220 221 222
}

static void
223
vorbis_encoder_close(Encoder *_encoder)
224
{
225
	struct vorbis_encoder &encoder = *(struct vorbis_encoder *)_encoder;
226 227 228 229 230

	vorbis_encoder_clear(encoder);
}

static void
231
vorbis_encoder_blockout(struct vorbis_encoder &encoder)
232
{
233 234 235
	while (vorbis_analysis_blockout(&encoder.vd, &encoder.vb) == 1) {
		vorbis_analysis(&encoder.vb, nullptr);
		vorbis_bitrate_addblock(&encoder.vb);
236

237
		ogg_packet packet;
238 239
		while (vorbis_bitrate_flushpacket(&encoder.vd, &packet))
			encoder.stream.PacketIn(packet);
240 241 242 243
	}
}

static bool
244
vorbis_encoder_flush(Encoder *_encoder, gcc_unused Error &error)
245
{
246
	struct vorbis_encoder &encoder = *(struct vorbis_encoder *)_encoder;
247

248
	encoder.stream.Flush();
249 250 251 252
	return true;
}

static bool
253
vorbis_encoder_pre_tag(Encoder *_encoder, gcc_unused Error &error)
254
{
255
	struct vorbis_encoder &encoder = *(struct vorbis_encoder *)_encoder;
256

257
	vorbis_analysis_wrote(&encoder.vd, 0);
258 259
	vorbis_encoder_blockout(encoder);

260 261
	/* reinitialize vorbis_dsp_state and vorbis_block to reset the
	   end-of-stream marker */
262 263 264 265
	vorbis_block_clear(&encoder.vb);
	vorbis_dsp_clear(&encoder.vd);
	vorbis_analysis_init(&encoder.vd, &encoder.vi);
	vorbis_block_init(&encoder.vd, &encoder.vb);
266

267
	encoder.stream.Flush();
268 269 270 271
	return true;
}

static void
272
copy_tag_to_vorbis_comment(vorbis_comment *vc, const Tag &tag)
273
{
274
	for (const auto &item : tag) {
275 276
		char name[64];
		ToUpperASCII(name, tag_item_names[item.type], sizeof(name));
Max Kellermann's avatar
Max Kellermann committed
277
		vorbis_comment_add_tag(vc, name, item.value);
278 279 280 281
	}
}

static bool
282
vorbis_encoder_tag(Encoder *_encoder, const Tag &tag,
283
		   gcc_unused Error &error)
284
{
285
	struct vorbis_encoder &encoder = *(struct vorbis_encoder *)_encoder;
286
	vorbis_comment comment;
287

288
	/* write the vorbis_comment object */
289

290
	vorbis_comment_init(&comment);
291
	copy_tag_to_vorbis_comment(&comment, tag);
292

293
	/* reset ogg_stream_state and begin a new stream */
294

295
	encoder.stream.Reinitialize(GenerateOggSerial());
296 297 298

	/* send that vorbis_comment to the ogg_stream_state */

299
	vorbis_encoder_headerout(encoder, comment);
300 301
	vorbis_comment_clear(&comment);

302 303 304 305
	return true;
}

static void
306 307
interleaved_to_vorbis_buffer(float **dest, const float *src,
			     unsigned num_frames, unsigned num_channels)
308 309 310
{
	for (unsigned i = 0; i < num_frames; i++)
		for (unsigned j = 0; j < num_channels; j++)
311
			dest[j][i] = *src++;
312 313 314
}

static bool
315
vorbis_encoder_write(Encoder *_encoder,
316
		     const void *data, size_t length,
317
		     gcc_unused Error &error)
318
{
319
	struct vorbis_encoder &encoder = *(struct vorbis_encoder *)_encoder;
320

321
	unsigned num_frames = length / encoder.audio_format.GetFrameSize();
322 323 324

	/* this is for only 16-bit audio */

325
	interleaved_to_vorbis_buffer(vorbis_analysis_buffer(&encoder.vd,
326 327 328
							    num_frames),
				     (const float *)data,
				     num_frames,
329
				     encoder.audio_format.channels);
330

331
	vorbis_analysis_wrote(&encoder.vd, num_frames);
332 333 334 335 336
	vorbis_encoder_blockout(encoder);
	return true;
}

static size_t
337
vorbis_encoder_read(Encoder *_encoder, void *dest, size_t length)
338
{
339
	struct vorbis_encoder &encoder = *(struct vorbis_encoder *)_encoder;
340

341
	return encoder.stream.PageOut(dest, length);
342 343
}

344
static const char *
345
vorbis_encoder_get_mime_type(gcc_unused Encoder *_encoder)
346
{
347
	return  "audio/ogg";
348 349
}

350
const EncoderPlugin vorbis_encoder_plugin = {
351 352 353 354 355 356 357 358 359 360 361 362
	"vorbis",
	vorbis_encoder_init,
	vorbis_encoder_finish,
	vorbis_encoder_open,
	vorbis_encoder_close,
	vorbis_encoder_pre_tag,
	vorbis_encoder_flush,
	vorbis_encoder_pre_tag,
	vorbis_encoder_tag,
	vorbis_encoder_write,
	vorbis_encoder_read,
	vorbis_encoder_get_mime_type,
363
};