VorbisEncoderPlugin.cxx 6.02 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
 * 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
#include "util/RuntimeError.hxx"
29 30 31

#include <vorbis/vorbisenc.h>

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

	vorbis_dsp_state vd;
	vorbis_block vb;
	vorbis_info vi;
38

39
public:
40
	VorbisEncoder(float quality, int bitrate, AudioFormat &_audio_format);
41

42
	virtual ~VorbisEncoder() {
43 44 45
		vorbis_block_clear(&vb);
		vorbis_dsp_clear(&vd);
		vorbis_info_clear(&vi);
46
	}
47

48
	/* virtual methods from class Encoder */
49 50
	void End() override {
		PreTag();
51
	}
52

53 54
	void PreTag() override;
	void SendTag(const Tag &tag) override;
55

56
	void Write(const void *data, size_t length) override;
57 58

private:
59 60 61
	void HeaderOut(vorbis_comment &vc);
	void SendHeader();
	void BlockOut();
62 63
};

64
class PreparedVorbisEncoder final : public PreparedEncoder {
65
	float quality = 3;
66 67
	int bitrate;

68
public:
69
	PreparedVorbisEncoder(const ConfigBlock &block);
70 71

	/* virtual methods from class PreparedEncoder */
72
	Encoder *Open(AudioFormat &audio_format) override;
73 74 75 76

	const char *GetMimeType() const override {
		return "audio/ogg";
	}
77 78
};

79
PreparedVorbisEncoder::PreparedVorbisEncoder(const ConfigBlock &block)
80
{
81
	const char *value = block.GetBlockValue("quality");
82
	if (value != nullptr) {
83 84
		/* a quality was configured (VBR) */

85
		char *endptr;
86
		quality = ParseDouble(value, &endptr);
87

88 89 90 91 92 93 94
		if (*endptr != '\0' || quality < -1.0 || quality > 10.0)
			throw FormatRuntimeError("quality \"%s\" is not a number in the "
						 "range -1 to 10",
						 value);

		if (block.GetBlockValue("bitrate") != nullptr)
			throw std::runtime_error("quality and bitrate are both defined");
95 96 97
	} else {
		/* a bit rate was configured */

98
		value = block.GetBlockValue("bitrate");
99
		if (value == nullptr)
100
			return;
101

102
		quality = -2.0;
103

104
		char *endptr;
105
		bitrate = ParseInt(value, &endptr);
106 107
		if (*endptr != '\0' || bitrate <= 0)
			throw std::runtime_error("bitrate should be a positive integer");
108 109 110
	}
}

111
static PreparedEncoder *
112
vorbis_encoder_init(const ConfigBlock &block)
113
{
114
	return new PreparedVorbisEncoder(block);
115 116
}

117 118 119
VorbisEncoder::VorbisEncoder(float quality, int bitrate,
			     AudioFormat &_audio_format)
	:OggEncoder(true)
120
{
121 122
	vorbis_info_init(&vi);

123 124 125
	_audio_format.format = SampleFormat::FLOAT;
	audio_format = _audio_format;

126
	if (quality >= -1.0) {
127 128
		/* a quality was configured (VBR) */

129 130 131 132
		if (0 != vorbis_encode_init_vbr(&vi,
						audio_format.channels,
						audio_format.sample_rate,
						quality * 0.1)) {
133 134
			vorbis_info_clear(&vi);
			throw std::runtime_error("error initializing vorbis vbr");
135 136 137 138
		}
	} else {
		/* a bit rate was configured */

139 140 141 142
		if (0 != vorbis_encode_init(&vi,
					    audio_format.channels,
					    audio_format.sample_rate, -1.0,
					    bitrate * 1000, -1.0)) {
143 144
			vorbis_info_clear(&vi);
			throw std::runtime_error("error initializing vorbis encoder");
145 146 147
		}
	}

148 149
	vorbis_analysis_init(&vd, &vi);
	vorbis_block_init(&vd, &vb);
150

151
	SendHeader();
152 153
}

154 155
void
VorbisEncoder::HeaderOut(vorbis_comment &vc)
156 157 158
{
	ogg_packet packet, comments, codebooks;

159
	vorbis_analysis_headerout(&vd, &vc,
160 161
				  &packet, &comments, &codebooks);

162 163 164
	stream.PacketIn(packet);
	stream.PacketIn(comments);
	stream.PacketIn(codebooks);
165
}
166

167 168
void
VorbisEncoder::SendHeader()
169
{
170
	VorbisComment vc;
171
	HeaderOut(vc);
172 173
}

174
Encoder *
175
PreparedVorbisEncoder::Open(AudioFormat &audio_format)
176
{
177
	return new VorbisEncoder(quality, bitrate, audio_format);
178 179
}

180 181
void
VorbisEncoder::BlockOut()
182
{
183 184 185
	while (vorbis_analysis_blockout(&vd, &vb) == 1) {
		vorbis_analysis(&vb, nullptr);
		vorbis_bitrate_addblock(&vb);
186

187
		ogg_packet packet;
188 189
		while (vorbis_bitrate_flushpacket(&vd, &packet))
			stream.PacketIn(packet);
190 191 192
	}
}

193 194
void
VorbisEncoder::PreTag()
195
{
196 197
	vorbis_analysis_wrote(&vd, 0);
	BlockOut();
198

199 200
	/* reinitialize vorbis_dsp_state and vorbis_block to reset the
	   end-of-stream marker */
201 202 203 204
	vorbis_block_clear(&vb);
	vorbis_dsp_clear(&vd);
	vorbis_analysis_init(&vd, &vi);
	vorbis_block_init(&vd, &vb);
205

206
	Flush();
207 208 209
}

static void
210
copy_tag_to_vorbis_comment(VorbisComment &vc, const Tag &tag)
211
{
212
	for (const auto &item : tag) {
213 214
		char name[64];
		ToUpperASCII(name, tag_item_names[item.type], sizeof(name));
215
		vc.AddTag(name, item.value);
216 217 218
	}
}

219 220
void
VorbisEncoder::SendTag(const Tag &tag)
221
{
222
	/* write the vorbis_comment object */
223

224 225
	VorbisComment comment;
	copy_tag_to_vorbis_comment(comment, tag);
226

227
	/* reset ogg_stream_state and begin a new stream */
228

229
	stream.Reinitialize(GenerateOggSerial());
230 231 232

	/* send that vorbis_comment to the ogg_stream_state */

233
	HeaderOut(comment);
234 235 236
}

static void
237 238
interleaved_to_vorbis_buffer(float **dest, const float *src,
			     unsigned num_frames, unsigned num_channels)
239 240 241
{
	for (unsigned i = 0; i < num_frames; i++)
		for (unsigned j = 0; j < num_channels; j++)
242
			dest[j][i] = *src++;
243 244
}

245 246
void
VorbisEncoder::Write(const void *data, size_t length)
247
{
248
	unsigned num_frames = length / audio_format.GetFrameSize();
249 250 251

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

252
	interleaved_to_vorbis_buffer(vorbis_analysis_buffer(&vd, num_frames),
253 254
				     (const float *)data,
				     num_frames,
255
				     audio_format.channels);
256

257 258
	vorbis_analysis_wrote(&vd, num_frames);
	BlockOut();
259 260
}

261
const EncoderPlugin vorbis_encoder_plugin = {
262 263
	"vorbis",
	vorbis_encoder_init,
264
};