OpusDecoderPlugin.cxx 10.4 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright (C) 2003-2014 The Music Player Daemon Project
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 "config.h" /* must be first for large file support */
#include "OpusDecoderPlugin.h"
22
#include "OpusDomain.hxx"
23 24
#include "OpusHead.hxx"
#include "OpusTags.hxx"
25
#include "OggFind.hxx"
26
#include "OggSyncState.hxx"
27
#include "../DecoderAPI.hxx"
28
#include "OggCodec.hxx"
29
#include "tag/TagHandler.hxx"
30
#include "tag/TagBuilder.hxx"
Max Kellermann's avatar
Max Kellermann committed
31
#include "input/InputStream.hxx"
32
#include "util/Error.hxx"
33
#include "Log.hxx"
34 35 36 37

#include <opus.h>
#include <ogg/ogg.h>

Max Kellermann's avatar
Max Kellermann committed
38
#include <string.h>
39

40
static constexpr opus_int32 opus_sample_rate = 48000;
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56

gcc_pure
static bool
IsOpusHead(const ogg_packet &packet)
{
	return packet.bytes >= 8 && memcmp(packet.packet, "OpusHead", 8) == 0;
}

gcc_pure
static bool
IsOpusTags(const ogg_packet &packet)
{
	return packet.bytes >= 8 && memcmp(packet.packet, "OpusTags", 8) == 0;
}

static bool
57
mpd_opus_init(gcc_unused const config_param &param)
58
{
59
	LogDebug(opus_domain, opus_get_version_string());
60 61 62 63 64

	return true;
}

class MPDOpusDecoder {
65
	Decoder &decoder;
66
	InputStream &input_stream;
67 68 69

	ogg_stream_state os;

70 71 72
	OpusDecoder *opus_decoder;
	opus_int16 *output_buffer;
	unsigned output_size;
73

74 75
	bool os_initialized;
	bool found_opus;
76 77 78

	int opus_serialno;

79 80
	ogg_int64_t eos_granulepos;

81 82 83
	size_t frame_size;

public:
84
	MPDOpusDecoder(Decoder &_decoder,
85
		       InputStream &_input_stream)
86 87 88 89
		:decoder(_decoder), input_stream(_input_stream),
		 opus_decoder(nullptr),
		 output_buffer(nullptr), output_size(0),
		 os_initialized(false), found_opus(false) {}
90 91
	~MPDOpusDecoder();

92 93
	bool ReadFirstPage(OggSyncState &oy);
	bool ReadNextPage(OggSyncState &oy);
94

95 96 97 98 99
	DecoderCommand HandlePackets();
	DecoderCommand HandlePacket(const ogg_packet &packet);
	DecoderCommand HandleBOS(const ogg_packet &packet);
	DecoderCommand HandleTags(const ogg_packet &packet);
	DecoderCommand HandleAudio(const ogg_packet &packet);
100 101

	bool Seek(OggSyncState &oy, double where);
102 103 104 105
};

MPDOpusDecoder::~MPDOpusDecoder()
{
106
	delete[] output_buffer;
107 108 109 110 111 112 113 114

	if (opus_decoder != nullptr)
		opus_decoder_destroy(opus_decoder);

	if (os_initialized)
		ogg_stream_clear(&os);
}

115
inline bool
116
MPDOpusDecoder::ReadFirstPage(OggSyncState &oy)
117
{
118 119
	assert(!os_initialized);

120
	if (!oy.ExpectFirstPage(os))
121 122 123 124 125 126 127
		return false;

	os_initialized = true;
	return true;
}

inline bool
128
MPDOpusDecoder::ReadNextPage(OggSyncState &oy)
129 130 131 132
{
	assert(os_initialized);

	ogg_page page;
133
	if (!oy.ExpectPage(page))
134 135
		return false;

136
	const auto page_serialno = ogg_page_serialno(&page);
137
	if (page_serialno != os.serialno)
138 139 140
		ogg_stream_reset_serialno(&os, page_serialno);

	ogg_stream_pagein(&os, &page);
141
	return true;
142 143
}

144
inline DecoderCommand
145 146
MPDOpusDecoder::HandlePackets()
{
147 148
	ogg_packet packet;
	while (ogg_stream_packetout(&os, &packet) == 1) {
149 150
		auto cmd = HandlePacket(packet);
		if (cmd != DecoderCommand::NONE)
151 152 153
			return cmd;
	}

154
	return DecoderCommand::NONE;
155 156
}

157
inline DecoderCommand
158 159 160
MPDOpusDecoder::HandlePacket(const ogg_packet &packet)
{
	if (packet.e_o_s)
161
		return DecoderCommand::STOP;
162 163 164 165

	if (packet.b_o_s)
		return HandleBOS(packet);
	else if (!found_opus)
166
		return DecoderCommand::STOP;
167 168 169 170 171 172 173

	if (IsOpusTags(packet))
		return HandleTags(packet);

	return HandleAudio(packet);
}

174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
/**
 * Load the end-of-stream packet and restore the previous file
 * position.
 */
static bool
LoadEOSPacket(InputStream &is, Decoder *decoder, int serialno,
	      ogg_packet &packet)
{
	if (!is.CheapSeeking())
		/* we do this for local files only, because seeking
		   around remote files is expensive and not worth the
		   troubl */
		return -1;

	const auto old_offset = is.offset;
	if (old_offset < 0)
		return -1;

	/* create temporary Ogg objects for seeking and parsing the
	   EOS packet */
	OggSyncState oy(is, decoder);
	ogg_stream_state os;
	ogg_stream_init(&os, serialno);

	bool result = OggSeekFindEOS(oy, os, packet, is);
	ogg_stream_clear(&os);

	/* restore the previous file position */
	is.Seek(old_offset, SEEK_SET, IgnoreError());

	return result;
}

/**
 * Load the end-of-stream granulepos and restore the previous file
 * position.
 *
 * @return -1 on error
 */
gcc_pure
static ogg_int64_t
LoadEOSGranulePos(InputStream &is, Decoder *decoder, int serialno)
{
	ogg_packet packet;
	if (!LoadEOSPacket(is, decoder, serialno, packet))
		return -1;

	return packet.granulepos;
}

224
inline DecoderCommand
225 226 227 228 229
MPDOpusDecoder::HandleBOS(const ogg_packet &packet)
{
	assert(packet.b_o_s);

	if (found_opus || !IsOpusHead(packet))
230
		return DecoderCommand::STOP;
231 232 233 234

	unsigned channels;
	if (!ScanOpusHeader(packet.packet, packet.bytes, channels) ||
	    !audio_valid_channel_count(channels))
235
		return DecoderCommand::STOP;
236 237 238 239 240 241 242 243 244 245 246 247 248 249

	assert(opus_decoder == nullptr);
	assert(output_buffer == nullptr);

	opus_serialno = os.serialno;
	found_opus = true;

	/* TODO: parse attributes from the OpusHead (sample rate,
	   channels, ...) */

	int opus_error;
	opus_decoder = opus_decoder_create(opus_sample_rate, channels,
					   &opus_error);
	if (opus_decoder == nullptr) {
250 251
		FormatError(opus_domain, "libopus error: %s",
			    opus_strerror(opus_error));
252
		return DecoderCommand::STOP;
253 254
	}

255 256
	eos_granulepos = LoadEOSGranulePos(input_stream, &decoder,
					   opus_serialno);
257 258 259 260
	const double duration = eos_granulepos >= 0
		? double(eos_granulepos) / opus_sample_rate
		: -1.0;

261 262
	const AudioFormat audio_format(opus_sample_rate,
				       SampleFormat::S16, channels);
263 264
	decoder_initialized(decoder, audio_format,
			    eos_granulepos > 0, duration);
265
	frame_size = audio_format.GetFrameSize();
266 267 268 269 270

	/* allocate an output buffer for 16 bit PCM samples big enough
	   to hold a quarter second, larger than 120ms required by
	   libopus */
	output_size = audio_format.sample_rate / 4;
271
	output_buffer = new opus_int16[output_size * audio_format.channels];
272 273 274 275

	return decoder_get_command(decoder);
}

276
inline DecoderCommand
277 278
MPDOpusDecoder::HandleTags(const ogg_packet &packet)
{
279
	ReplayGainInfo rgi;
280
	rgi.Clear();
281

282
	TagBuilder tag_builder;
283

284
	DecoderCommand cmd;
Max Kellermann's avatar
Max Kellermann committed
285
	if (ScanOpusTags(packet.packet, packet.bytes,
286
			 &rgi,
287 288
			 &add_tag_handler, &tag_builder) &&
	    !tag_builder.IsEmpty()) {
289 290
		decoder_replay_gain(decoder, &rgi);

291
		Tag tag = tag_builder.Commit();
292
		cmd = decoder_tag(decoder, input_stream, std::move(tag));
293
	} else
294 295 296 297 298
		cmd = decoder_get_command(decoder);

	return cmd;
}

299
inline DecoderCommand
300 301 302 303 304 305 306 307 308 309
MPDOpusDecoder::HandleAudio(const ogg_packet &packet)
{
	assert(opus_decoder != nullptr);

	int nframes = opus_decode(opus_decoder,
				  (const unsigned char*)packet.packet,
				  packet.bytes,
				  output_buffer, output_size,
				  0);
	if (nframes < 0) {
310
		LogError(opus_domain, opus_strerror(nframes));
311
		return DecoderCommand::STOP;
312 313 314 315
	}

	if (nframes > 0) {
		const size_t nbytes = nframes * frame_size;
316 317 318 319
		auto cmd = decoder_data(decoder, input_stream,
					output_buffer, nbytes,
					0);
		if (cmd != DecoderCommand::NONE)
320
			return cmd;
321 322 323 324 325

		if (packet.granulepos > 0)
			decoder_timestamp(decoder,
					  double(packet.granulepos)
					  / opus_sample_rate);
326 327
	}

328
	return DecoderCommand::NONE;
329 330
}

331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353
bool
MPDOpusDecoder::Seek(OggSyncState &oy, double where_s)
{
	assert(eos_granulepos > 0);
	assert(input_stream.seekable);
	assert(input_stream.size > 0);
	assert(input_stream.offset >= 0);

	const ogg_int64_t where_granulepos(where_s * opus_sample_rate);

	/* interpolate the file offset where we expect to find the
	   given granule position */
	/* TODO: implement binary search */
	InputStream::offset_type offset(where_granulepos * input_stream.size
					/ eos_granulepos);

	if (!OggSeekPageAtOffset(oy, os, input_stream, offset, SEEK_SET))
		return false;

	decoder_timestamp(decoder, where_s);
	return true;
}

354
static void
355
mpd_opus_stream_decode(Decoder &decoder,
356
		       InputStream &input_stream)
357
{
358
	if (ogg_codec_detect(&decoder, input_stream) != OGG_CODEC_OPUS)
359 360 361 362
		return;

	/* rewind the stream, because ogg_codec_detect() has
	   moved it */
363
	input_stream.LockRewind(IgnoreError());
364 365

	MPDOpusDecoder d(decoder, input_stream);
366
	OggSyncState oy(input_stream, &decoder);
367

368
	if (!d.ReadFirstPage(oy))
369 370
		return;

371
	while (true) {
372
		auto cmd = d.HandlePackets();
373 374 375 376 377 378 379 380 381
		if (cmd == DecoderCommand::SEEK) {
			if (d.Seek(oy, decoder_seek_where(decoder)))
				decoder_command_finished(decoder);
			else
				decoder_seek_error(decoder);

			continue;
		}

382
		if (cmd != DecoderCommand::NONE)
383 384
			break;

385
		if (!d.ReadNextPage(oy))
386
			break;
387 388 389 390
	}
}

static bool
391
mpd_opus_scan_stream(InputStream &is,
392 393
		     const struct tag_handler *handler, void *handler_ctx)
{
394
	OggSyncState oy(is);
395

396
	ogg_stream_state os;
397
	if (!oy.ExpectFirstPage(os))
398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416
		return false;

	/* read at most two more pages */
	unsigned remaining_pages = 2;

	bool result = false;

	ogg_packet packet;
	while (true) {
		int r = ogg_stream_packetout(&os, &packet);
		if (r < 0) {
			result = false;
			break;
		}

		if (r == 0) {
			if (remaining_pages-- == 0)
				break;

417
			if (!oy.ExpectPageIn(os)) {
418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440
				result = false;
				break;
			}

			continue;
		}

		if (packet.b_o_s) {
			if (!IsOpusHead(packet))
				break;

			unsigned channels;
			if (!ScanOpusHeader(packet.packet, packet.bytes, channels) ||
			    !audio_valid_channel_count(channels)) {
				result = false;
				break;
			}

			result = true;
		} else if (!result)
			break;
		else if (IsOpusTags(packet)) {
			if (!ScanOpusTags(packet.packet, packet.bytes,
441
					  nullptr,
442 443 444 445 446 447 448
					  handler, handler_ctx))
				result = false;

			break;
		}
	}

449
	if (packet.e_o_s || OggSeekFindEOS(oy, os, packet, is))
450 451 452
		tag_handler_invoke_duration(handler, handler_ctx,
					    packet.granulepos / opus_sample_rate);

453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469
	ogg_stream_clear(&os);

	return result;
}

static const char *const opus_suffixes[] = {
	"opus",
	"ogg",
	"oga",
	nullptr
};

static const char *const opus_mime_types[] = {
	"audio/opus",
	nullptr
};

470
const struct DecoderPlugin opus_decoder_plugin = {
471 472 473 474 475 476 477 478 479 480 481
	"opus",
	mpd_opus_init,
	nullptr,
	mpd_opus_stream_decode,
	nullptr,
	nullptr,
	mpd_opus_scan_stream,
	nullptr,
	opus_suffixes,
	opus_mime_types,
};