LameEncoderPlugin.cxx 5.65 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 "LameEncoderPlugin.hxx"
21
#include "../EncoderAPI.hxx"
22
#include "pcm/AudioFormat.hxx"
23
#include "util/NumberParser.hxx"
24
#include "util/ReusableArray.hxx"
25
#include "util/RuntimeError.hxx"
26 27

#include <lame/lame.h>
28

29
#include <cassert>
30 31
#include <stdexcept>

32 33
#include <string.h>

34 35
class LameEncoder final : public Encoder {
	const AudioFormat audio_format;
36

37 38 39 40 41 42 43
	lame_global_flags *const gfp;

	ReusableArray<unsigned char, 32768> output_buffer;
	unsigned char *output_begin = nullptr, *output_end = nullptr;

public:
	LameEncoder(const AudioFormat _audio_format,
44
		    lame_global_flags *_gfp) noexcept
45 46 47
		:Encoder(false),
		 audio_format(_audio_format), gfp(_gfp) {}

48
	~LameEncoder() noexcept override;
49

50 51 52
	LameEncoder(const LameEncoder &) = delete;
	LameEncoder &operator=(const LameEncoder &) = delete;

53
	/* virtual methods from class Encoder */
54
	void Write(const void *data, size_t length) override;
55
	size_t Read(void *dest, size_t length) noexcept override;
56
};
57

58
class PreparedLameEncoder final : public PreparedEncoder {
59 60
	float quality;
	int bitrate;
61

62
public:
Max Kellermann's avatar
Max Kellermann committed
63
	explicit PreparedLameEncoder(const ConfigBlock &block);
64 65

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

68
	[[nodiscard]] const char *GetMimeType() const noexcept override {
69 70
		return "audio/mpeg";
	}
71
};
72

73
PreparedLameEncoder::PreparedLameEncoder(const ConfigBlock &block)
74 75 76 77
{
	const char *value;
	char *endptr;

78
	value = block.GetBlockValue("quality");
79
	if (value != nullptr) {
80 81
		/* a quality was configured (VBR) */

Rosen Penev's avatar
Rosen Penev committed
82
		quality = float(ParseDouble(value, &endptr));
83

Rosen Penev's avatar
Rosen Penev committed
84
		if (*endptr != '\0' || quality < -1.0f || quality > 10.0f)
85 86 87
			throw FormatRuntimeError("quality \"%s\" is not a number in the "
						 "range -1 to 10",
						 value);
88

89 90
		if (block.GetBlockValue("bitrate") != nullptr)
			throw std::runtime_error("quality and bitrate are both defined");
91 92 93
	} else {
		/* a bit rate was configured */

94
		value = block.GetBlockValue("bitrate");
95 96
		if (value == nullptr)
			throw std::runtime_error("neither bitrate nor quality defined");
97

98
		quality = -2.0;
99
		bitrate = ParseInt(value, &endptr);
100

101 102
		if (*endptr != '\0' || bitrate <= 0)
			throw std::runtime_error("bitrate should be a positive integer");
103 104 105
	}
}

106
static PreparedEncoder *
107
lame_encoder_init(const ConfigBlock &block)
108
{
109
	return new PreparedLameEncoder(block);
110 111
}

112
static void
113
lame_encoder_setup(lame_global_flags *gfp, float quality, int bitrate,
114
		   const AudioFormat &audio_format)
115
{
Rosen Penev's avatar
Rosen Penev committed
116
	if (quality >= -1.0f) {
117 118
		/* a quality was configured (VBR) */

119 120 121
		if (0 != lame_set_VBR(gfp, vbr_rh))
			throw std::runtime_error("error setting lame VBR mode");

Rosen Penev's avatar
Rosen Penev committed
122
		if (0 != lame_set_VBR_q(gfp, int(quality)))
123
			throw std::runtime_error("error setting lame VBR quality");
124 125 126
	} else {
		/* a bit rate was configured */

127 128
		if (0 != lame_set_brate(gfp, bitrate))
			throw std::runtime_error("error setting lame bitrate");
129 130
	}

131 132
	if (0 != lame_set_num_channels(gfp, audio_format.channels))
		throw std::runtime_error("error setting lame num channels");
133

134 135
	if (0 != lame_set_in_samplerate(gfp, audio_format.sample_rate))
		throw std::runtime_error("error setting lame sample rate");
136

137 138
	if (0 != lame_set_out_samplerate(gfp, audio_format.sample_rate))
		throw std::runtime_error("error setting lame out sample rate");
139

140 141
	if (0 > lame_init_params(gfp))
		throw std::runtime_error("error initializing lame params");
142 143
}

144
Encoder *
145
PreparedLameEncoder::Open(AudioFormat &audio_format)
146
{
147 148
	audio_format.format = SampleFormat::S16;
	audio_format.channels = 2;
149

150
	auto gfp = lame_init();
151 152
	if (gfp == nullptr)
		throw std::runtime_error("lame_init() failed");
153

154 155 156
	try {
		lame_encoder_setup(gfp, quality, bitrate, audio_format);
	} catch (...) {
157
		lame_close(gfp);
158
		throw;
159 160
	}

161
	return new LameEncoder(audio_format, gfp);
162 163
}

164
LameEncoder::~LameEncoder() noexcept
165
{
166
	lame_close(gfp);
167 168
}

169 170
void
LameEncoder::Write(const void *data, size_t length)
171
{
Max Kellermann's avatar
Max Kellermann committed
172
	const auto *src = (const int16_t*)data;
173

174
	assert(output_begin == output_end);
175

176 177
	const unsigned num_frames = length / audio_format.GetFrameSize();
	const unsigned num_samples = length / audio_format.GetSampleSize();
178 179 180

	/* worst-case formula according to LAME documentation */
	const size_t output_buffer_size = 5 * num_samples / 4 + 7200;
181
	const auto dest = output_buffer.Get(output_buffer_size);
182 183 184

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

185
	int bytes_out = lame_encode_buffer_interleaved(gfp,
186 187
						       const_cast<short *>(src),
						       num_frames,
188
						       dest, output_buffer_size);
189

190 191
	if (bytes_out < 0)
		throw std::runtime_error("lame encoder failed");
192

193 194
	output_begin = dest;
	output_end = dest + bytes_out;
195 196
}

197
size_t
198
LameEncoder::Read(void *dest, size_t length) noexcept
199
{
200 201 202
	const auto begin = output_begin;
	assert(begin <= output_end);
	const size_t remainning = output_end - begin;
203 204
	if (length > remainning)
		length = remainning;
205

206
	memcpy(dest, begin, length);
207

208
	output_begin = begin + length;
209 210 211
	return length;
}

212
const EncoderPlugin lame_encoder_plugin = {
213 214
	"lame",
	lame_encoder_init,
215
};