VorbisEncoderPlugin.cxx 6.85 KB
Newer Older
1
/*
2
 * Copyright 2003-2016 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 "OggEncoder.hxx"
23
#include "lib/xiph/VorbisComment.hxx"
24
#include "AudioFormat.hxx"
25
#include "config/ConfigError.hxx"
26
#include "util/StringUtil.hxx"
27
#include "util/NumberParser.hxx"
28 29
#include "util/Error.hxx"
#include "util/Domain.hxx"
30 31 32

#include <vorbis/vorbisenc.h>

33
class VorbisEncoder final : public OggEncoder {
34
	AudioFormat audio_format;
35 36 37 38

	vorbis_dsp_state vd;
	vorbis_block vb;
	vorbis_info vi;
39

40 41
public:
	VorbisEncoder()
42 43 44
		:OggEncoder(true) {
		vorbis_info_init(&vi);
	}
45

46
	virtual ~VorbisEncoder() {
47 48 49
		vorbis_block_clear(&vb);
		vorbis_dsp_clear(&vd);
		vorbis_info_clear(&vi);
50
	}
51

52 53 54 55 56 57 58
	bool Open(float quality, int bitrate, AudioFormat &audio_format,
		  Error &error);

	/* virtual methods from class Encoder */
	bool End(Error &error) override {
		return PreTag(error);
	}
59

60 61 62 63 64 65
	bool PreTag(Error &error) override;
	bool SendTag(const Tag &tag, Error &error) override;

	bool Write(const void *data, size_t length, Error &) override;

private:
66 67 68
	void HeaderOut(vorbis_comment &vc);
	void SendHeader();
	void BlockOut();
69 70
};

71
class PreparedVorbisEncoder final : public PreparedEncoder {
72 73 74
	float quality;
	int bitrate;

75
public:
76
	bool Configure(const ConfigBlock &block, Error &error);
77 78 79 80 81 82 83

	/* virtual methods from class PreparedEncoder */
	Encoder *Open(AudioFormat &audio_format, Error &) override;

	const char *GetMimeType() const override {
		return "audio/ogg";
	}
84 85
};

86
static constexpr Domain vorbis_encoder_domain("vorbis_encoder");
87

88
bool
89
PreparedVorbisEncoder::Configure(const ConfigBlock &block, Error &error)
90
{
91
	const char *value = block.GetBlockValue("quality");
92
	if (value != nullptr) {
93 94
		/* a quality was configured (VBR) */

95
		char *endptr;
96
		quality = ParseDouble(value, &endptr);
97

98
		if (*endptr != '\0' || quality < -1.0 || quality > 10.0) {
99 100
			error.Format(config_domain,
				     "quality \"%s\" is not a number in the "
101 102
				     "range -1 to 10",
				     value);
103 104 105
			return false;
		}

106
		if (block.GetBlockValue("bitrate") != nullptr) {
107 108
			error.Set(config_domain,
				  "quality and bitrate are both defined");
109 110 111 112 113
			return false;
		}
	} else {
		/* a bit rate was configured */

114
		value = block.GetBlockValue("bitrate");
115
		if (value == nullptr) {
116 117
			error.Set(config_domain,
				  "neither bitrate nor quality defined");
118 119 120
			return false;
		}

121
		quality = -2.0;
122

123
		char *endptr;
124 125
		bitrate = ParseInt(value, &endptr);
		if (*endptr != '\0' || bitrate <= 0) {
126 127
			error.Set(config_domain,
				  "bitrate should be a positive integer");
128 129 130 131 132 133 134
			return false;
		}
	}

	return true;
}

135
static PreparedEncoder *
136
vorbis_encoder_init(const ConfigBlock &block, Error &error)
137
{
138
	auto *encoder = new PreparedVorbisEncoder();
139

140
	/* load configuration from "block" */
141
	if (!encoder->Configure(block, error)) {
142
		/* configuration has failed, roll back and return error */
143
		delete encoder;
144
		return nullptr;
145 146
	}

147
	return encoder;
148 149
}

150
bool
151 152
VorbisEncoder::Open(float quality, int bitrate, AudioFormat &_audio_format,
		    Error &error)
153
{
154 155 156
	_audio_format.format = SampleFormat::FLOAT;
	audio_format = _audio_format;

157
	if (quality >= -1.0) {
158 159
		/* a quality was configured (VBR) */

160 161 162 163
		if (0 != vorbis_encode_init_vbr(&vi,
						audio_format.channels,
						audio_format.sample_rate,
						quality * 0.1)) {
164 165
			error.Set(vorbis_encoder_domain,
				  "error initializing vorbis vbr");
166 167 168 169 170
			return false;
		}
	} else {
		/* a bit rate was configured */

171 172 173 174
		if (0 != vorbis_encode_init(&vi,
					    audio_format.channels,
					    audio_format.sample_rate, -1.0,
					    bitrate * 1000, -1.0)) {
175 176
			error.Set(vorbis_encoder_domain,
				  "error initializing vorbis encoder");
177 178 179 180
			return false;
		}
	}

181 182
	vorbis_analysis_init(&vd, &vi);
	vorbis_block_init(&vd, &vb);
183

184 185
	SendHeader();

186 187 188
	return true;
}

189 190
void
VorbisEncoder::HeaderOut(vorbis_comment &vc)
191 192 193
{
	ogg_packet packet, comments, codebooks;

194
	vorbis_analysis_headerout(&vd, &vc,
195 196
				  &packet, &comments, &codebooks);

197 198 199
	stream.PacketIn(packet);
	stream.PacketIn(comments);
	stream.PacketIn(codebooks);
200
}
201

202 203
void
VorbisEncoder::SendHeader()
204
{
205
	VorbisComment vc;
206
	HeaderOut(vc);
207 208
}

209 210
Encoder *
PreparedVorbisEncoder::Open(AudioFormat &audio_format, Error &error)
211
{
212
	auto *e = new VorbisEncoder();
213
	if (!e->Open(quality, bitrate, audio_format, error)) {
214 215 216
		delete e;
		return nullptr;
	}
217

218
	return e;
219 220
}

221 222
void
VorbisEncoder::BlockOut()
223
{
224 225 226
	while (vorbis_analysis_blockout(&vd, &vb) == 1) {
		vorbis_analysis(&vb, nullptr);
		vorbis_bitrate_addblock(&vb);
227

228
		ogg_packet packet;
229 230
		while (vorbis_bitrate_flushpacket(&vd, &packet))
			stream.PacketIn(packet);
231 232 233
	}
}

234 235
bool
VorbisEncoder::PreTag(gcc_unused Error &error)
236
{
237 238
	vorbis_analysis_wrote(&vd, 0);
	BlockOut();
239

240 241
	/* reinitialize vorbis_dsp_state and vorbis_block to reset the
	   end-of-stream marker */
242 243 244 245
	vorbis_block_clear(&vb);
	vorbis_dsp_clear(&vd);
	vorbis_analysis_init(&vd, &vi);
	vorbis_block_init(&vd, &vb);
246

247
	Flush();
248 249 250 251
	return true;
}

static void
252
copy_tag_to_vorbis_comment(VorbisComment &vc, const Tag &tag)
253
{
254
	for (const auto &item : tag) {
255 256
		char name[64];
		ToUpperASCII(name, tag_item_names[item.type], sizeof(name));
257
		vc.AddTag(name, item.value);
258 259 260
	}
}

261 262
bool
VorbisEncoder::SendTag(const Tag &tag, gcc_unused Error &error)
263
{
264
	/* write the vorbis_comment object */
265

266 267
	VorbisComment comment;
	copy_tag_to_vorbis_comment(comment, tag);
268

269
	/* reset ogg_stream_state and begin a new stream */
270

271
	stream.Reinitialize(GenerateOggSerial());
272 273 274

	/* send that vorbis_comment to the ogg_stream_state */

275
	HeaderOut(comment);
276

277 278 279 280
	return true;
}

static void
281 282
interleaved_to_vorbis_buffer(float **dest, const float *src,
			     unsigned num_frames, unsigned num_channels)
283 284 285
{
	for (unsigned i = 0; i < num_frames; i++)
		for (unsigned j = 0; j < num_channels; j++)
286
			dest[j][i] = *src++;
287 288
}

289 290
bool
VorbisEncoder::Write(const void *data, size_t length, gcc_unused Error &error)
291
{
292
	unsigned num_frames = length / audio_format.GetFrameSize();
293 294 295

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

296
	interleaved_to_vorbis_buffer(vorbis_analysis_buffer(&vd, num_frames),
297 298
				     (const float *)data,
				     num_frames,
299
				     audio_format.channels);
300

301 302
	vorbis_analysis_wrote(&vd, num_frames);
	BlockOut();
303 304 305
	return true;
}

306
const EncoderPlugin vorbis_encoder_plugin = {
307 308
	"vorbis",
	vorbis_encoder_init,
309
};