VorbisEncoderPlugin.cxx 6.15 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2021 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 "VorbisEncoderPlugin.hxx"
21
#include "OggEncoder.hxx"
22
#include "lib/xiph/VorbisComment.hxx"
23
#include "pcm/AudioFormat.hxx"
24
#include "config/Domain.hxx"
25
#include "util/StringUtil.hxx"
26
#include "util/NumberParser.hxx"
27
#include "util/RuntimeError.hxx"
28 29 30

#include <vorbis/vorbisenc.h>

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

	vorbis_dsp_state vd;
	vorbis_block vb;
	vorbis_info vi;
37

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

41
	~VorbisEncoder() noexcept override {
42 43 44
		vorbis_block_clear(&vb);
		vorbis_dsp_clear(&vd);
		vorbis_info_clear(&vi);
45
	}
46

47 48 49
	VorbisEncoder(const VorbisEncoder &) = delete;
	VorbisEncoder &operator=(const VorbisEncoder &) = delete;

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

55 56
	void PreTag() override;
	void SendTag(const Tag &tag) override;
57

58
	void Write(const void *data, size_t length) override;
59 60

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

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

70
public:
Max Kellermann's avatar
Max Kellermann committed
71
	explicit PreparedVorbisEncoder(const ConfigBlock &block);
72 73

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

76
	[[nodiscard]] const char *GetMimeType() const noexcept override {
77 78
		return "audio/ogg";
	}
79 80
};

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

87
		char *endptr;
88
		quality = ParseDouble(value, &endptr);
89

Rosen Penev's avatar
Rosen Penev committed
90
		if (*endptr != '\0' || quality < -1.0f || quality > 10.0f)
91 92 93 94 95 96
			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");
97 98 99
	} else {
		/* a bit rate was configured */

100
		value = block.GetBlockValue("bitrate");
101
		if (value == nullptr)
102
			return;
103

104
		quality = -2.0;
105

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

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

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

125 126 127
	_audio_format.format = SampleFormat::FLOAT;
	audio_format = _audio_format;

Rosen Penev's avatar
Rosen Penev committed
128
	if (quality >= -1.0f) {
129 130
		/* a quality was configured (VBR) */

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

141 142 143
		if (0 != vorbis_encode_init(&vi,
					    audio_format.channels,
					    audio_format.sample_rate, -1.0,
Rosen Penev's avatar
Rosen Penev committed
144
					    bitrate * 1000, -1.0f)) {
145 146
			vorbis_info_clear(&vi);
			throw std::runtime_error("error initializing vorbis encoder");
147 148 149
		}
	}

150 151
	vorbis_analysis_init(&vd, &vi);
	vorbis_block_init(&vd, &vb);
152

153
	SendHeader();
154 155
}

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

161
	vorbis_analysis_headerout(&vd, &vc,
162 163
				  &packet, &comments, &codebooks);

164 165 166
	stream.PacketIn(packet);
	stream.PacketIn(comments);
	stream.PacketIn(codebooks);
167
}
168

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

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

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

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

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

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

208
	Flush();
209 210 211
}

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

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

226 227
	VorbisComment comment;
	copy_tag_to_vorbis_comment(comment, tag);
228

229
	/* reset ogg_stream_state and begin a new stream */
230

231
	stream.Reinitialize(GenerateSerial());
232 233 234

	/* send that vorbis_comment to the ogg_stream_state */

235
	HeaderOut(comment);
236 237 238
}

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

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

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

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

259 260
	vorbis_analysis_wrote(&vd, num_frames);
	BlockOut();
261 262
}

263
const EncoderPlugin vorbis_encoder_plugin = {
264 265
	"vorbis",
	vorbis_encoder_init,
266
};