VorbisDecoderPlugin.cxx 9.54 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2017 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 "config.h"
21
#include "VorbisDecoderPlugin.h"
22
#include "OggDecoder.hxx"
23
#include "lib/xiph/VorbisComments.hxx"
24 25
#include "lib/xiph/OggPacket.hxx"
#include "lib/xiph/OggFind.hxx"
26
#include "VorbisDomain.hxx"
27
#include "../DecoderAPI.hxx"
Max Kellermann's avatar
Max Kellermann committed
28
#include "input/InputStream.hxx"
29
#include "input/Reader.hxx"
30
#include "OggCodec.hxx"
31
#include "pcm/Interleave.hxx"
32
#include "util/Macros.hxx"
33
#include "util/ScopeExit.hxx"
34
#include "CheckAudioFormat.hxx"
35
#include "tag/TagHandler.hxx"
36
#include "Log.hxx"
37 38

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

44
#include <stdexcept>
45

46 47 48 49 50 51 52 53 54 55
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;
	typedef float in_sample_t;
	typedef float out_sample_t;
#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 89
private:
	void InitVorbis() {
		vorbis_info_init(&vi);
		vorbis_comment_init(&vc);
	}
90

91 92 93
	void DeinitVorbis() {
		if (dsp_initialized) {
			dsp_initialized = false;
94

95 96 97
			vorbis_block_clear(&block);
			vorbis_dsp_clear(&dsp);
		}
98

99 100 101
		vorbis_comment_clear(&vc);
		vorbis_info_clear(&vi);
	}
102

103 104 105 106
	void ReinitVorbis() {
		DeinitVorbis();
		InitVorbis();
	}
107

108 109 110
	void SubmitInit();
	bool SubmitSomePcm();
	void SubmitPcm();
111

112 113 114 115 116 117
protected:
	/* virtual methods from class OggVisitor */
	void OnOggBeginning(const ogg_packet &packet) override;
	void OnOggPacket(const ogg_packet &packet) override;
	void OnOggEnd() override;
};
118

119 120 121 122 123 124 125 126 127
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);

128 129 130 131 132
	try {
		SeekGranulePos(where_granulepos);
		vorbis_synthesis_restart(&dsp);
		return true;
	} catch (const std::runtime_error &) {
133
		return false;
134
	}
135 136
}

137 138
void
VorbisDecoder::OnOggBeginning(const ogg_packet &_packet)
Avuton Olrich's avatar
Avuton Olrich committed
139
{
140 141 142 143 144 145 146 147 148
	/* libvorbis wants non-const packets */
	ogg_packet &packet = const_cast<ogg_packet &>(_packet);

	ReinitVorbis();

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

	remaining_header_packets = 2;
149 150
}

151
static void
152
vorbis_send_comments(DecoderClient &client, InputStream &is,
153
		     char **comments)
Avuton Olrich's avatar
Avuton Olrich committed
154
{
155 156 157
	Tag *tag = vorbis_comments_to_tag(comments);
	if (!tag)
		return;
158

159
	client.SubmitTag(is, std::move(*tag));
160
	delete tag;
161 162
}

163 164
void
VorbisDecoder::SubmitInit()
165
{
166
	assert(!dsp_initialized);
167

168
	audio_format = CheckAudioFormat(vi.rate, sample_format, vi.channels);
169

170
	frame_size = audio_format.GetFrameSize();
171

172 173 174 175 176
	const auto eos_granulepos = UpdateEndGranulePos();
	const auto duration = eos_granulepos >= 0
		? SignedSongTime::FromScale<uint64_t>(eos_granulepos,
						      audio_format.sample_rate)
		: SignedSongTime::Negative();
177

178
	client.Ready(audio_format, eos_granulepos > 0, duration);
179 180
}

181 182 183 184 185 186 187 188 189 190 191 192 193 194
#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

195 196
bool
VorbisDecoder::SubmitSomePcm()
197
{
198 199 200
	in_sample_t **pcm;
	int result = vorbis_synthesis_pcmout(&dsp, &pcm);
	if (result <= 0)
201
		return false;
202 203 204 205 206 207 208 209 210 211 212 213

	out_sample_t buffer[4096];
	const unsigned channels = audio_format.channels;
	size_t max_frames = ARRAY_SIZE(buffer) / channels;
	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) {
214
			*dest = tremor_clip_sample(*src++);
215 216
			dest += channels;
		}
217
	}
218 219 220 221 222 223 224 225 226 227
#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;
228 229 230
	auto cmd = client.SubmitData(input_stream,
				     buffer, nbytes,
				     0);
231 232
	if (cmd != DecoderCommand::NONE)
		throw cmd;
233 234 235 236

	return true;
}

237 238
void
VorbisDecoder::SubmitPcm()
239
{
240
	while (SubmitSomePcm()) {}
241 242
}

243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
void
VorbisDecoder::OnOggPacket(const ogg_packet &_packet)
{
	/* libvorbis wants non-const packets */
	ogg_packet &packet = const_cast<ogg_packet &>(_packet);

	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();

265
		vorbis_send_comments(client, input_stream, vc.user_comments);
266 267 268

		ReplayGainInfo rgi;
		if (vorbis_comments_to_replay_gain(rgi, vc.user_comments))
269
			client.SubmitReplayGain(&rgi);
270 271 272 273 274 275 276 277 278 279 280
	} 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 */
281
			auto cmd = client.GetCommand();
282 283 284 285 286 287 288 289 290 291
			if (cmd != DecoderCommand::NONE)
				throw cmd;
			return;
		}

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

		SubmitPcm();

292
#ifndef HAVE_TREMOR
293
		if (packet.granulepos > 0)
294
			client.SubmitTimestamp(vorbis_granule_time(&dsp, packet.granulepos));
295 296 297 298 299 300
#endif
	}
}

void
VorbisDecoder::OnOggEnd()
301 302 303
{
}

304
/* public */
305 306

static bool
307
vorbis_init(gcc_unused const ConfigBlock &block)
308 309 310 311 312 313 314
{
#ifndef HAVE_TREMOR
	LogDebug(vorbis_domain, vorbis_version_string());
#endif
	return true;
}

315
static void
316
vorbis_stream_decode(DecoderClient &client,
317
		     InputStream &input_stream)
Warren Dukes's avatar
Warren Dukes committed
318
{
319
	if (ogg_codec_detect(&client, input_stream) != OGG_CODEC_VORBIS)
320
		return;
321

322
	/* rewind the stream, because ogg_codec_detect() has
323
	   moved it */
324 325 326 327
	try {
		input_stream.LockRewind();
	} catch (const std::runtime_error &) {
	}
328

329
	DecoderReader reader(client, input_stream);
330
	VorbisDecoder d(reader);
331

332 333 334
	while (true) {
		try {
			d.Visit();
335
			break;
336 337
		} catch (DecoderCommand cmd) {
			if (cmd == DecoderCommand::SEEK) {
338 339
				if (d.Seek(client.GetSeekFrame()))
					client.CommandFinished();
340
				else
341
					client.SeekError();
342
			} else if (cmd != DecoderCommand::NONE)
343 344
				break;
		}
345
	}
Warren Dukes's avatar
Warren Dukes committed
346 347
}

348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364
static void
VisitVorbisDuration(InputStream &is,
		    OggSyncState &sync, OggStreamState &stream,
		    unsigned sample_rate,
		    const TagHandler &handler, void *handler_ctx)
{
	ogg_packet packet;

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

	const auto duration =
		SongTime::FromScale<uint64_t>(packet.granulepos,
					      sample_rate);
	tag_handler_invoke_duration(handler, handler_ctx, duration);
}

365
static bool
366
vorbis_scan_stream(InputStream &is,
367
		   const TagHandler &handler, void *handler_ctx)
Avuton Olrich's avatar
Avuton Olrich committed
368
{
369
	/* initialize libogg */
Warren Dukes's avatar
Warren Dukes committed
370

371 372 373 374 375
	InputStreamReader reader(is);
	OggSyncState sync(reader);

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

378
	OggStreamState stream(first_page);
Warren Dukes's avatar
Warren Dukes committed
379

380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401
	/* 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 */

	vorbis_comments_scan(vc.user_comments,
402
			     handler, handler_ctx);
Warren Dukes's avatar
Warren Dukes committed
403

404 405 406 407
	/* check the song duration by locating the e_o_s packet */

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

408
	return true;
Warren Dukes's avatar
Warren Dukes committed
409 410
}

Max Kellermann's avatar
Max Kellermann committed
411
static const char *const vorbis_suffixes[] = {
412
	"ogg", "oga", nullptr
Max Kellermann's avatar
Max Kellermann committed
413 414 415
};

static const char *const vorbis_mime_types[] = {
416 417
	"application/ogg",
	"application/x-ogg",
418 419 420 421 422 423
	"audio/ogg",
	"audio/vorbis",
	"audio/vorbis+ogg",
	"audio/x-ogg",
	"audio/x-vorbis",
	"audio/x-vorbis+ogg",
424
	nullptr
425
};
Warren Dukes's avatar
Warren Dukes committed
426

427
const struct DecoderPlugin vorbis_decoder_plugin = {
428
	"vorbis",
429
	vorbis_init,
430 431 432 433 434 435 436 437
	nullptr,
	vorbis_stream_decode,
	nullptr,
	nullptr,
	vorbis_scan_stream,
	nullptr,
	vorbis_suffixes,
	vorbis_mime_types
Warren Dukes's avatar
Warren Dukes committed
438
};