ShineEncoderPlugin.cxx 5.47 KB
Newer Older
Andrée Ekroth's avatar
Andrée Ekroth committed
1
/*
2
 * Copyright 2003-2016 The Music Player Daemon Project
Andrée Ekroth's avatar
Andrée Ekroth committed
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
 * 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.
 *
 * 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.
 */

#include "ShineEncoderPlugin.hxx"
#include "config.h"
22
#include "../EncoderAPI.hxx"
Andrée Ekroth's avatar
Andrée Ekroth committed
23
#include "AudioFormat.hxx"
24
#include "config/ConfigError.hxx"
Andrée Ekroth's avatar
Andrée Ekroth committed
25 26 27 28 29 30 31 32 33 34 35
#include "util/DynamicFifoBuffer.hxx"
#include "util/Error.hxx"

extern "C"
{
#include <shine/layer3.h>
}

static constexpr size_t BUFFER_INIT_SIZE = 8192;
static constexpr unsigned CHANNELS = 2;

36 37
class ShineEncoder final : public Encoder {
	const AudioFormat audio_format;
Andrée Ekroth's avatar
Andrée Ekroth committed
38

39
	const shine_t shine;
Andrée Ekroth's avatar
Andrée Ekroth committed
40

41
	const size_t frame_size;
Andrée Ekroth's avatar
Andrée Ekroth committed
42

43 44 45
	/* workaround for bug:
	   https://github.com/savonet/shine/issues/11 */
	size_t input_pos = SHINE_MAX_SAMPLES + 1;
Andrée Ekroth's avatar
Andrée Ekroth committed
46 47 48

	int16_t *stereo[CHANNELS];

49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
	DynamicFifoBuffer<uint8_t> output_buffer;

public:
	ShineEncoder(AudioFormat _audio_format, shine_t _shine)
		:Encoder(false),
		 audio_format(_audio_format), shine(_shine),
		 frame_size(shine_samples_per_pass(shine)),
		 stereo{new int16_t[frame_size], new int16_t[frame_size]},
		 output_buffer(BUFFER_INIT_SIZE) {}

	~ShineEncoder() override {
		if (input_pos > SHINE_MAX_SAMPLES) {
			/* write zero chunk */
			input_pos = 0;
			WriteChunk(true);
		}

		shine_close(shine);
		delete[] stereo[0];
		delete[] stereo[1];
	}

	bool WriteChunk(bool flush);

	/* virtual methods from class Encoder */
	bool End(Error &error) override {
		return Flush(error);
	}

	bool Flush(Error &) override;

	bool Write(const void *data, size_t length, Error &) override;

	size_t Read(void *dest, size_t length) override {
		return output_buffer.Read((uint8_t *)dest, length);
	}
};
Andrée Ekroth's avatar
Andrée Ekroth committed
86

87
class PreparedShineEncoder final : public PreparedEncoder {
88 89
	shine_config_t config;

90
public:
91
	bool Configure(const ConfigBlock &block, Error &error);
Andrée Ekroth's avatar
Andrée Ekroth committed
92

93 94 95 96 97 98
	/* virtual methods from class PreparedEncoder */
	Encoder *Open(AudioFormat &audio_format, Error &) override;

	const char *GetMimeType() const override {
		return  "audio/mpeg";
	}
Andrée Ekroth's avatar
Andrée Ekroth committed
99 100 101
};

inline bool
102
PreparedShineEncoder::Configure(const ConfigBlock &block, Error &)
Andrée Ekroth's avatar
Andrée Ekroth committed
103 104
{
	shine_set_config_mpeg_defaults(&config.mpeg);
105
	config.mpeg.bitr = block.GetBlockValue("bitrate", 128);
Andrée Ekroth's avatar
Andrée Ekroth committed
106 107 108 109

	return true;
}

110
static PreparedEncoder *
111
shine_encoder_init(const ConfigBlock &block, Error &error)
Andrée Ekroth's avatar
Andrée Ekroth committed
112
{
113
	auto *encoder = new PreparedShineEncoder();
Andrée Ekroth's avatar
Andrée Ekroth committed
114

115 116
	/* load configuration from "block" */
	if (!encoder->Configure(block, error)) {
Andrée Ekroth's avatar
Andrée Ekroth committed
117 118 119 120 121
		/* configuration has failed, roll back and return error */
		delete encoder;
		return nullptr;
	}

122
	return encoder;
Andrée Ekroth's avatar
Andrée Ekroth committed
123 124
}

125 126 127
static shine_t
SetupShine(shine_config_t config, AudioFormat &audio_format,
	   Error &error)
Andrée Ekroth's avatar
Andrée Ekroth committed
128
{
129 130 131
	audio_format.format = SampleFormat::S16;
	audio_format.channels = CHANNELS;

Andrée Ekroth's avatar
Andrée Ekroth committed
132 133
	config.mpeg.mode = audio_format.channels == 2 ? STEREO : MONO;
	config.wave.samplerate = audio_format.sample_rate;
134 135
	config.wave.channels =
		audio_format.channels == 2 ? PCM_STEREO : PCM_MONO;
Andrée Ekroth's avatar
Andrée Ekroth committed
136 137 138

	if (shine_check_config(config.wave.samplerate, config.mpeg.bitr) < 0) {
		error.Format(config_domain,
139 140 141
			     "error configuring shine. "
			     "samplerate %d and bitrate %d configuration"
			     " not supported.",
Andrée Ekroth's avatar
Andrée Ekroth committed
142 143 144
			     config.wave.samplerate,
			     config.mpeg.bitr);

145
		return nullptr;
Andrée Ekroth's avatar
Andrée Ekroth committed
146 147
	}

148 149
	auto shine = shine_initialise(&config);
	if (!shine)
Andrée Ekroth's avatar
Andrée Ekroth committed
150 151 152
		error.Format(config_domain,
			     "error initializing shine.");

153
	return shine;
Andrée Ekroth's avatar
Andrée Ekroth committed
154 155
}

156 157
Encoder *
PreparedShineEncoder::Open(AudioFormat &audio_format, Error &error)
Andrée Ekroth's avatar
Andrée Ekroth committed
158
{
159
	auto shine = SetupShine(config, audio_format, error);
160 161
	if (!shine)
		return nullptr;
162

163
	return new ShineEncoder(audio_format, shine);
Andrée Ekroth's avatar
Andrée Ekroth committed
164 165 166
}

bool
167
ShineEncoder::WriteChunk(bool flush)
Andrée Ekroth's avatar
Andrée Ekroth committed
168
{
169
	if (flush || input_pos == frame_size) {
Andrée Ekroth's avatar
Andrée Ekroth committed
170 171
		if (flush) {
			/* fill remaining with 0s */
172 173
			for (; input_pos < frame_size; input_pos++) {
				stereo[0][input_pos] = stereo[1][input_pos] = 0;
Andrée Ekroth's avatar
Andrée Ekroth committed
174 175 176
			}
		}

177
		int written;
178 179
		const uint8_t *out =
			shine_encode_buffer(shine, stereo, &written);
Andrée Ekroth's avatar
Andrée Ekroth committed
180 181

		if (written > 0)
182
			output_buffer.Append(out, written);
183 184

		input_pos = 0;
Andrée Ekroth's avatar
Andrée Ekroth committed
185 186 187 188 189
	}

	return true;
}

190 191
bool
ShineEncoder::Write(const void *_data, size_t length, gcc_unused Error &error)
Andrée Ekroth's avatar
Andrée Ekroth committed
192 193
{
	const int16_t *data = (const int16_t*)_data;
194
	length /= sizeof(*data) * audio_format.channels;
195 196
	size_t written = 0;

197 198
	if (input_pos > SHINE_MAX_SAMPLES)
		input_pos = 0;
199

200 201 202
	/* write all data to de-interleaved buffers */
	while (written < length) {
		for (;
203 204
		     written < length && input_pos < frame_size;
		     written++, input_pos++) {
205
			const size_t base =
206 207 208
				written * audio_format.channels;
			stereo[0][input_pos] = data[base];
			stereo[1][input_pos] = data[base + 1];
209 210
		}
		/* write if chunk is filled */
211
		WriteChunk(false);
212
	}
Andrée Ekroth's avatar
Andrée Ekroth committed
213

214
	return true;
Andrée Ekroth's avatar
Andrée Ekroth committed
215 216
}

217 218
bool
ShineEncoder::Flush(gcc_unused Error &error)
Andrée Ekroth's avatar
Andrée Ekroth committed
219
{
220
	/* flush buffers and flush shine */
221
	WriteChunk(true);
222 223

	int written;
224
	const uint8_t *data = shine_flush(shine, &written);
Andrée Ekroth's avatar
Andrée Ekroth committed
225 226

	if (written > 0)
227
		output_buffer.Append(data, written);
Andrée Ekroth's avatar
Andrée Ekroth committed
228 229 230 231 232 233 234 235

	return true;
}

const EncoderPlugin shine_encoder_plugin = {
	"shine",
	shine_encoder_init,
};