LameEncoderPlugin.cxx 5.55 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

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

55
class PreparedLameEncoder final : public PreparedEncoder {
56 57
	float quality;
	int bitrate;
58

59
public:
Max Kellermann's avatar
Max Kellermann committed
60
	explicit PreparedLameEncoder(const ConfigBlock &block);
61 62

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

65
	[[nodiscard]] const char *GetMimeType() const noexcept override {
66 67
		return "audio/mpeg";
	}
68
};
69

70
PreparedLameEncoder::PreparedLameEncoder(const ConfigBlock &block)
71 72 73 74
{
	const char *value;
	char *endptr;

75
	value = block.GetBlockValue("quality");
76
	if (value != nullptr) {
77 78
		/* a quality was configured (VBR) */

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

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

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

91
		value = block.GetBlockValue("bitrate");
92 93
		if (value == nullptr)
			throw std::runtime_error("neither bitrate nor quality defined");
94

95
		quality = -2.0;
96
		bitrate = ParseInt(value, &endptr);
97

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

103
static PreparedEncoder *
104
lame_encoder_init(const ConfigBlock &block)
105
{
106
	return new PreparedLameEncoder(block);
107 108
}

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

116 117 118
		if (0 != lame_set_VBR(gfp, vbr_rh))
			throw std::runtime_error("error setting lame VBR mode");

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

124 125
		if (0 != lame_set_brate(gfp, bitrate))
			throw std::runtime_error("error setting lame bitrate");
126 127
	}

128 129
	if (0 != lame_set_num_channels(gfp, audio_format.channels))
		throw std::runtime_error("error setting lame num channels");
130

131 132
	if (0 != lame_set_in_samplerate(gfp, audio_format.sample_rate))
		throw std::runtime_error("error setting lame sample rate");
133

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

137 138
	if (0 > lame_init_params(gfp))
		throw std::runtime_error("error initializing lame params");
139 140
}

141
Encoder *
142
PreparedLameEncoder::Open(AudioFormat &audio_format)
143
{
144 145
	audio_format.format = SampleFormat::S16;
	audio_format.channels = 2;
146

147
	auto gfp = lame_init();
148 149
	if (gfp == nullptr)
		throw std::runtime_error("lame_init() failed");
150

151 152 153
	try {
		lame_encoder_setup(gfp, quality, bitrate, audio_format);
	} catch (...) {
154
		lame_close(gfp);
155
		throw;
156 157
	}

158
	return new LameEncoder(audio_format, gfp);
159 160
}

161
LameEncoder::~LameEncoder() noexcept
162
{
163
	lame_close(gfp);
164 165
}

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

171
	assert(output_begin == output_end);
172

173 174
	const unsigned num_frames = length / audio_format.GetFrameSize();
	const unsigned num_samples = length / audio_format.GetSampleSize();
175 176 177

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

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

182
	int bytes_out = lame_encode_buffer_interleaved(gfp,
183 184
						       const_cast<short *>(src),
						       num_frames,
185
						       dest, output_buffer_size);
186

187 188
	if (bytes_out < 0)
		throw std::runtime_error("lame encoder failed");
189

190 191
	output_begin = dest;
	output_end = dest + bytes_out;
192 193
}

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

203
	memcpy(dest, begin, length);
204

205
	output_begin = begin + length;
206 207 208
	return length;
}

209
const EncoderPlugin lame_encoder_plugin = {
210 211
	"lame",
	lame_encoder_init,
212
};