VorbisEncoderPlugin.cxx 6.03 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2020 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
	/* virtual methods from class Encoder */
48 49
	void End() override {
		PreTag();
50
	}
51

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

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

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

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

67
public:
Max Kellermann's avatar
Max Kellermann committed
68
	explicit PreparedVorbisEncoder(const ConfigBlock &block);
69 70

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

73
	const char *GetMimeType() const noexcept override {
74 75
		return "audio/ogg";
	}
76 77
};

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

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

87 88 89 90 91 92 93
		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");
94 95 96
	} else {
		/* a bit rate was configured */

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

101
		quality = -2.0;
102

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

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

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

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

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

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

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

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

150
	SendHeader();
151 152
}

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

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

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

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

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

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

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

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

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

205
	Flush();
206 207 208
}

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

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

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

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

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

	/* send that vorbis_comment to the ogg_stream_state */

232
	HeaderOut(comment);
233 234 235
}

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

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

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

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

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

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