VorbisDecoderPlugin.cxx 9.61 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2020 The Music Player Daemon Project
3
 * http://www.musicpd.org
Warren Dukes's avatar
Warren Dukes committed
4 5 6 7 8 9 10 11 12 13
 *
 * 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.
Warren Dukes's avatar
Warren Dukes committed
18 19
 */

20
#include "VorbisDecoderPlugin.h"
21
#include "OggDecoder.hxx"
22
#include "lib/xiph/VorbisComments.hxx"
23 24
#include "lib/xiph/OggPacket.hxx"
#include "lib/xiph/OggFind.hxx"
25
#include "VorbisDomain.hxx"
26
#include "../DecoderAPI.hxx"
27
#include "decoder/Features.h"
Max Kellermann's avatar
Max Kellermann committed
28
#include "input/InputStream.hxx"
29
#include "input/Reader.hxx"
30
#include "OggCodec.hxx"
31
#include "pcm/CheckAudioFormat.hxx"
32
#include "pcm/Interleave.hxx"
33
#include "util/ScopeExit.hxx"
34
#include "tag/Handler.hxx"
35
#include "Log.hxx"
36 37

#ifndef HAVE_TREMOR
38
#include <vorbis/codec.h>
39
#else
40
#include <tremor/ivorbiscodec.h>
Avuton Olrich's avatar
Avuton Olrich committed
41
#endif /* HAVE_TREMOR */
42

43
#include <iterator>
44
#include <stdexcept>
45

46 47 48 49 50 51 52
class VorbisDecoder final : public OggDecoder {
#ifdef HAVE_TREMOR
	static constexpr SampleFormat sample_format = SampleFormat::S16;
	typedef ogg_int32_t in_sample_t;
	typedef int16_t out_sample_t;
#else
	static constexpr SampleFormat sample_format = SampleFormat::FLOAT;
53 54
	using in_sample_t = float;
	using out_sample_t = float;
55
#endif
56

57
	unsigned remaining_header_packets;
58

59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
	vorbis_info vi;
	vorbis_comment vc;
	vorbis_dsp_state dsp;
	vorbis_block block;

	/**
	 * If non-zero, then a previous Vorbis stream has been found
	 * already with this number of channels.
	 */
	AudioFormat audio_format = AudioFormat::Undefined();
	size_t frame_size;

	bool dsp_initialized = false;

public:
	explicit VorbisDecoder(DecoderReader &reader)
		:OggDecoder(reader) {
		InitVorbis();
	}
78

79 80 81
	~VorbisDecoder() {
		DeinitVorbis();
	}
Max Kellermann's avatar
Max Kellermann committed
82

83
	bool Seek(uint64_t where_frame);
84

85 86 87 88
	static AudioFormat CheckAudioFormat(const vorbis_info &vi) {
		return ::CheckAudioFormat(vi.rate, sample_format, vi.channels);
	}

89
	[[nodiscard]] AudioFormat CheckAudioFormat() const {
90 91 92
		return CheckAudioFormat(vi);
	}

93 94 95 96 97
private:
	void InitVorbis() {
		vorbis_info_init(&vi);
		vorbis_comment_init(&vc);
	}
98

99 100 101
	void DeinitVorbis() {
		if (dsp_initialized) {
			dsp_initialized = false;
102

103 104 105
			vorbis_block_clear(&block);
			vorbis_dsp_clear(&dsp);
		}
106

107 108 109
		vorbis_comment_clear(&vc);
		vorbis_info_clear(&vi);
	}
110

111 112 113 114
	void ReinitVorbis() {
		DeinitVorbis();
		InitVorbis();
	}
115

116 117 118
	void SubmitInit();
	bool SubmitSomePcm();
	void SubmitPcm();
119

120 121 122 123 124 125
protected:
	/* virtual methods from class OggVisitor */
	void OnOggBeginning(const ogg_packet &packet) override;
	void OnOggPacket(const ogg_packet &packet) override;
	void OnOggEnd() override;
};
126

127 128 129 130 131 132 133 134 135
bool
VorbisDecoder::Seek(uint64_t where_frame)
{
	assert(IsSeekable());
	assert(input_stream.IsSeekable());
	assert(input_stream.KnownSize());

	const ogg_int64_t where_granulepos(where_frame);

136 137 138 139
	try {
		SeekGranulePos(where_granulepos);
		vorbis_synthesis_restart(&dsp);
		return true;
140
	} catch (...) {
141
		return false;
142
	}
143 144
}

145 146
void
VorbisDecoder::OnOggBeginning(const ogg_packet &_packet)
Avuton Olrich's avatar
Avuton Olrich committed
147
{
148
	/* libvorbis wants non-const packets */
Max Kellermann's avatar
Max Kellermann committed
149
	auto &packet = const_cast<ogg_packet &>(_packet);
150 151 152 153 154 155 156

	ReinitVorbis();

	if (vorbis_synthesis_headerin(&vi, &vc, &packet) != 0)
		throw std::runtime_error("Unrecognized Vorbis BOS packet");

	remaining_header_packets = 2;
157 158
}

159
static void
160 161
SubmitVorbisComment(DecoderClient &client, InputStream &is,
		    const vorbis_comment &vc)
Avuton Olrich's avatar
Avuton Olrich committed
162
{
163
	auto tag = VorbisCommentToTag(vc);
164 165
	if (!tag)
		return;
166

167
	client.SubmitTag(is, std::move(*tag));
168 169
}

170 171
void
VorbisDecoder::SubmitInit()
172
{
173
	assert(!dsp_initialized);
174

175
	audio_format = CheckAudioFormat(vi);
176

177
	frame_size = audio_format.GetFrameSize();
178

179 180 181 182 183
	const auto eos_granulepos = UpdateEndGranulePos();
	const auto duration = eos_granulepos >= 0
		? SignedSongTime::FromScale<uint64_t>(eos_granulepos,
						      audio_format.sample_rate)
		: SignedSongTime::Negative();
184

185
	client.Ready(audio_format, eos_granulepos > 0, duration);
186 187
}

188 189 190 191 192 193 194 195 196 197 198 199 200 201
#ifdef HAVE_TREMOR
static inline int16_t tremor_clip_sample(int32_t x)
{
	x >>= 9;

	if (x < INT16_MIN)
		return INT16_MIN;
	if (x > INT16_MAX)
		return INT16_MAX;

	return x;
}
#endif

202 203
bool
VorbisDecoder::SubmitSomePcm()
204
{
205 206 207
	in_sample_t **pcm;
	int result = vorbis_synthesis_pcmout(&dsp, &pcm);
	if (result <= 0)
208
		return false;
209 210 211

	out_sample_t buffer[4096];
	const unsigned channels = audio_format.channels;
212
	size_t max_frames = std::size(buffer) / channels;
213 214 215 216 217 218 219 220
	size_t n_frames = std::min(size_t(result), max_frames);

#ifdef HAVE_TREMOR
	for (unsigned c = 0; c < channels; ++c) {
		const auto *src = pcm[c];
		auto *dest = &buffer[c];

		for (size_t i = 0; i < n_frames; ++i) {
221
			*dest = tremor_clip_sample(*src++);
222 223
			dest += channels;
		}
224
	}
225 226 227 228 229 230 231 232 233 234
#else
	PcmInterleaveFloat(buffer,
			   ConstBuffer<const in_sample_t *>(pcm,
							    channels),
			   n_frames);
#endif

	vorbis_synthesis_read(&dsp, n_frames);

	const size_t nbytes = n_frames * frame_size;
235 236 237
	auto cmd = client.SubmitData(input_stream,
				     buffer, nbytes,
				     0);
238 239
	if (cmd != DecoderCommand::NONE)
		throw cmd;
240 241 242 243

	return true;
}

244 245
void
VorbisDecoder::SubmitPcm()
246
{
247
	while (SubmitSomePcm()) {}
248 249
}

250 251 252 253
void
VorbisDecoder::OnOggPacket(const ogg_packet &_packet)
{
	/* libvorbis wants non-const packets */
Max Kellermann's avatar
Max Kellermann committed
254
	auto &packet = const_cast<ogg_packet &>(_packet);
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271

	if (remaining_header_packets > 0) {
		if (vorbis_synthesis_headerin(&vi, &vc, &packet) != 0)
			throw std::runtime_error("Unrecognized Vorbis header packet");

		if (--remaining_header_packets > 0)
			return;

		if (audio_format.IsDefined()) {
			/* TODO: change the MPD decoder plugin API to
			   allow mid-song AudioFormat changes */
			if ((unsigned)vi.rate != audio_format.sample_rate ||
			    (unsigned)vi.channels != audio_format.channels)
				throw std::runtime_error("Next stream has different audio format");
		} else
			SubmitInit();

272
		SubmitVorbisComment(client, input_stream, vc);
273 274

		ReplayGainInfo rgi;
275
		if (VorbisCommentToReplayGain(rgi, vc))
276
			client.SubmitReplayGain(&rgi);
277 278 279 280 281 282 283 284 285 286 287
	} else {
		if (!dsp_initialized) {
			dsp_initialized = true;

			vorbis_synthesis_init(&dsp, &vi);
			vorbis_block_init(&dsp, &block);
		}

		if (vorbis_synthesis(&block, &packet) != 0) {
			/* ignore bad packets, but give the MPD core a
			   chance to stop us */
288
			auto cmd = client.GetCommand();
289 290 291 292 293 294 295 296 297 298
			if (cmd != DecoderCommand::NONE)
				throw cmd;
			return;
		}

		if (vorbis_synthesis_blockin(&dsp, &block) != 0)
			throw std::runtime_error("vorbis_synthesis_blockin() failed");

		SubmitPcm();

299
#ifndef HAVE_TREMOR
300
		if (packet.granulepos > 0)
301
			client.SubmitTimestamp(FloatDuration(vorbis_granule_time(&dsp, packet.granulepos)));
302 303 304 305 306 307
#endif
	}
}

void
VorbisDecoder::OnOggEnd()
308 309 310
{
}

311
/* public */
312 313

static bool
Rosen Penev's avatar
Rosen Penev committed
314
vorbis_init([[maybe_unused]] const ConfigBlock &block)
315 316 317 318 319 320 321
{
#ifndef HAVE_TREMOR
	LogDebug(vorbis_domain, vorbis_version_string());
#endif
	return true;
}

322
static void
323
vorbis_stream_decode(DecoderClient &client,
324
		     InputStream &input_stream)
Warren Dukes's avatar
Warren Dukes committed
325
{
326
	if (ogg_codec_detect(&client, input_stream) != OGG_CODEC_VORBIS)
327
		return;
328

329
	/* rewind the stream, because ogg_codec_detect() has
330
	   moved it */
331 332
	try {
		input_stream.LockRewind();
333
	} catch (...) {
334
	}
335

336
	DecoderReader reader(client, input_stream);
337
	VorbisDecoder d(reader);
338

339 340 341
	while (true) {
		try {
			d.Visit();
342
			break;
343 344
		} catch (DecoderCommand cmd) {
			if (cmd == DecoderCommand::SEEK) {
345 346
				if (d.Seek(client.GetSeekFrame()))
					client.CommandFinished();
347
				else
348
					client.SeekError();
349
			} else if (cmd != DecoderCommand::NONE)
350 351
				break;
		}
352
	}
Warren Dukes's avatar
Warren Dukes committed
353 354
}

355 356 357 358
static void
VisitVorbisDuration(InputStream &is,
		    OggSyncState &sync, OggStreamState &stream,
		    unsigned sample_rate,
359
		    TagHandler &handler) noexcept
360 361 362 363 364 365 366 367 368
{
	ogg_packet packet;

	if (!OggSeekFindEOS(sync, stream, packet, is))
		return;

	const auto duration =
		SongTime::FromScale<uint64_t>(packet.granulepos,
					      sample_rate);
369
	handler.OnDuration(duration);
370 371
}

372
static bool
373
vorbis_scan_stream(InputStream &is, TagHandler &handler)
Avuton Olrich's avatar
Avuton Olrich committed
374
{
375
	/* initialize libogg */
Warren Dukes's avatar
Warren Dukes committed
376

377 378 379 380 381
	InputStreamReader reader(is);
	OggSyncState sync(reader);

	ogg_page first_page;
	if (!sync.ExpectPage(first_page))
382
		return false;
Warren Dukes's avatar
Warren Dukes committed
383

384
	OggStreamState stream(first_page);
Warren Dukes's avatar
Warren Dukes committed
385

386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406
	/* initialize libvorbis */

	vorbis_info vi;
	vorbis_info_init(&vi);
	AtScopeExit(&) { vorbis_info_clear(&vi); };

	vorbis_comment vc;
	vorbis_comment_init(&vc);
	AtScopeExit(&) { vorbis_comment_clear(&vc); };

	/* feed the first 3 packets to libvorbis */

	for (unsigned i = 0; i < 3; ++i) {
		ogg_packet packet;
		if (!OggReadPacket(sync, stream, packet) ||
		    vorbis_synthesis_headerin(&vi, &vc, &packet) != 0)
			return false;
	}

	/* visit the Vorbis comments we just read */

407
	VorbisCommentScan(vc, handler);
Warren Dukes's avatar
Warren Dukes committed
408

409 410
	/* check the song duration by locating the e_o_s packet */

411
	VisitVorbisDuration(is, sync, stream, vi.rate, handler);
412

413 414 415 416 417
	try {
		handler.OnAudioFormat(VorbisDecoder::CheckAudioFormat(vi));
	} catch (...) {
	}

418
	return true;
Warren Dukes's avatar
Warren Dukes committed
419 420
}

Max Kellermann's avatar
Max Kellermann committed
421
static const char *const vorbis_suffixes[] = {
422
	"ogg", "oga", nullptr
Max Kellermann's avatar
Max Kellermann committed
423 424 425
};

static const char *const vorbis_mime_types[] = {
426 427
	"application/ogg",
	"application/x-ogg",
428 429 430 431 432 433
	"audio/ogg",
	"audio/vorbis",
	"audio/vorbis+ogg",
	"audio/x-ogg",
	"audio/x-vorbis",
	"audio/x-vorbis+ogg",
434
	nullptr
435
};
Warren Dukes's avatar
Warren Dukes committed
436

437 438 439 440 441
constexpr DecoderPlugin vorbis_decoder_plugin =
	DecoderPlugin("vorbis", vorbis_stream_decode, vorbis_scan_stream)
	.WithInit(vorbis_init)
	.WithSuffixes(vorbis_suffixes)
	.WithMimeTypes(vorbis_mime_types);