LameEncoderPlugin.cxx 6.94 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright (C) 2003-2014 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 "LameEncoderPlugin.hxx"
22
#include "../EncoderAPI.hxx"
23
#include "AudioFormat.hxx"
24
#include "config/ConfigError.hxx"
25
#include "util/NumberParser.hxx"
26
#include "util/ReusableArray.hxx"
27
#include "util/Manual.hxx"
28 29
#include "util/Error.hxx"
#include "util/Domain.hxx"
30 31

#include <lame/lame.h>
32

33 34 35
#include <assert.h>
#include <string.h>

36
struct LameEncoder final {
37
	Encoder encoder;
38

39
	AudioFormat audio_format;
40 41 42 43 44
	float quality;
	int bitrate;

	lame_global_flags *gfp;

45 46
	Manual<ReusableArray<unsigned char, 32768>> output_buffer;
	unsigned char *output_begin, *output_end;
47

48
	LameEncoder():encoder(lame_encoder_plugin) {}
49

50
	bool Configure(const config_param &param, Error &error);
51
};
52

53
static constexpr Domain lame_encoder_domain("lame_encoder");
54

55
bool
56
LameEncoder::Configure(const config_param &param, Error &error)
57 58 59 60
{
	const char *value;
	char *endptr;

61
	value = param.GetBlockValue("quality");
62
	if (value != nullptr) {
63 64
		/* a quality was configured (VBR) */

65
		quality = ParseDouble(value, &endptr);
66

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

75
		if (param.GetBlockValue("bitrate") != nullptr) {
76 77
			error.Set(config_domain,
				  "quality and bitrate are both defined");
78 79 80 81 82
			return false;
		}
	} else {
		/* a bit rate was configured */

83
		value = param.GetBlockValue("bitrate");
84
		if (value == nullptr) {
85 86
			error.Set(config_domain,
				  "neither bitrate nor quality defined");
87 88 89
			return false;
		}

90
		quality = -2.0;
91
		bitrate = ParseInt(value, &endptr);
92

93
		if (*endptr != '\0' || bitrate <= 0) {
94 95
			error.Set(config_domain,
				  "bitrate should be a positive integer");
96 97 98 99 100 101 102
			return false;
		}
	}

	return true;
}

103
static Encoder *
104
lame_encoder_init(const config_param &param, Error &error)
105
{
106
	LameEncoder *encoder = new LameEncoder();
107 108

	/* load configuration from "param" */
109
	if (!encoder->Configure(param, error)) {
110
		/* configuration has failed, roll back and return error */
111 112
		delete encoder;
		return nullptr;
113 114 115 116 117 118
	}

	return &encoder->encoder;
}

static void
119
lame_encoder_finish(Encoder *_encoder)
120
{
121
	LameEncoder *encoder = (LameEncoder *)_encoder;
122 123 124

	/* the real liblame cleanup was already performed by
	   lame_encoder_close(), so no real work here */
125
	delete encoder;
126 127 128
}

static bool
129
lame_encoder_setup(LameEncoder *encoder, Error &error)
130 131 132 133 134
{
	if (encoder->quality >= -1.0) {
		/* a quality was configured (VBR) */

		if (0 != lame_set_VBR(encoder->gfp, vbr_rh)) {
135 136
			error.Set(lame_encoder_domain,
				  "error setting lame VBR mode");
137 138 139
			return false;
		}
		if (0 != lame_set_VBR_q(encoder->gfp, encoder->quality)) {
140 141
			error.Set(lame_encoder_domain,
				  "error setting lame VBR quality");
142 143 144 145 146 147
			return false;
		}
	} else {
		/* a bit rate was configured */

		if (0 != lame_set_brate(encoder->gfp, encoder->bitrate)) {
148 149
			error.Set(lame_encoder_domain,
				  "error setting lame bitrate");
150 151 152 153 154 155
			return false;
		}
	}

	if (0 != lame_set_num_channels(encoder->gfp,
				       encoder->audio_format.channels)) {
156 157
		error.Set(lame_encoder_domain,
			  "error setting lame num channels");
158 159 160 161 162
		return false;
	}

	if (0 != lame_set_in_samplerate(encoder->gfp,
					encoder->audio_format.sample_rate)) {
163 164
		error.Set(lame_encoder_domain,
			  "error setting lame sample rate");
165 166 167
		return false;
	}

168 169
	if (0 != lame_set_out_samplerate(encoder->gfp,
					 encoder->audio_format.sample_rate)) {
170 171
		error.Set(lame_encoder_domain,
			  "error setting lame out sample rate");
172 173 174
		return false;
	}

175
	if (0 > lame_init_params(encoder->gfp)) {
176 177
		error.Set(lame_encoder_domain,
			  "error initializing lame params");
178 179 180 181 182 183 184
		return false;
	}

	return true;
}

static bool
185
lame_encoder_open(Encoder *_encoder, AudioFormat &audio_format, Error &error)
186
{
187
	LameEncoder *encoder = (LameEncoder *)_encoder;
188

189 190
	audio_format.format = SampleFormat::S16;
	audio_format.channels = 2;
191

192
	encoder->audio_format = audio_format;
193 194

	encoder->gfp = lame_init();
195
	if (encoder->gfp == nullptr) {
196
		error.Set(lame_encoder_domain, "lame_init() failed");
197 198 199 200 201 202 203 204
		return false;
	}

	if (!lame_encoder_setup(encoder, error)) {
		lame_close(encoder->gfp);
		return false;
	}

205
	encoder->output_buffer.Construct();
206
	encoder->output_begin = encoder->output_end = nullptr;
207 208 209 210 211

	return true;
}

static void
212
lame_encoder_close(Encoder *_encoder)
213
{
214
	LameEncoder *encoder = (LameEncoder *)_encoder;
215 216

	lame_close(encoder->gfp);
217
	encoder->output_buffer.Destruct();
218 219 220
}

static bool
221
lame_encoder_write(Encoder *_encoder,
222
		   const void *data, size_t length,
223
		   gcc_unused Error &error)
224
{
225
	LameEncoder *encoder = (LameEncoder *)_encoder;
226 227
	const int16_t *src = (const int16_t*)data;

228
	assert(encoder->output_begin == encoder->output_end);
229

230
	const unsigned num_frames =
231
		length / encoder->audio_format.GetFrameSize();
232 233 234 235 236
	const unsigned num_samples =
		length / encoder->audio_format.GetSampleSize();

	/* worst-case formula according to LAME documentation */
	const size_t output_buffer_size = 5 * num_samples / 4 + 7200;
237
	const auto output_buffer = encoder->output_buffer->Get(output_buffer_size);
238 239 240

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

241 242 243
	int bytes_out = lame_encode_buffer_interleaved(encoder->gfp,
						       const_cast<short *>(src),
						       num_frames,
244 245
						       output_buffer,
						       output_buffer_size);
246 247

	if (bytes_out < 0) {
248
		error.Set(lame_encoder_domain, "lame encoder failed");
249 250 251
		return false;
	}

252 253
	encoder->output_begin = output_buffer;
	encoder->output_end = output_buffer + bytes_out;
254 255 256 257
	return true;
}

static size_t
258
lame_encoder_read(Encoder *_encoder, void *dest, size_t length)
259
{
260
	LameEncoder *encoder = (LameEncoder *)_encoder;
261

262 263 264
	const auto begin = encoder->output_begin;
	assert(begin <= encoder->output_end);
	const size_t remainning = encoder->output_end - begin;
265 266
	if (length > remainning)
		length = remainning;
267

268
	memcpy(dest, begin, length);
269

270
	encoder->output_begin = begin + length;
271 272 273
	return length;
}

274
static const char *
275
lame_encoder_get_mime_type(gcc_unused Encoder *_encoder)
276
{
277
	return "audio/mpeg";
278 279
}

280
const EncoderPlugin lame_encoder_plugin = {
281 282 283 284 285 286 287 288 289 290 291 292
	"lame",
	lame_encoder_init,
	lame_encoder_finish,
	lame_encoder_open,
	lame_encoder_close,
	nullptr,
	nullptr,
	nullptr,
	nullptr,
	lame_encoder_write,
	lame_encoder_read,
	lame_encoder_get_mime_type,
293
};