FlacDecoderPlugin.cxx 9.3 KB
Newer Older
1
/*
2
 * Copyright 2003-2016 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" /* must be first for large file support */
21
#include "FlacDecoderPlugin.h"
22
#include "FlacStreamDecoder.hxx"
23
#include "FlacDomain.hxx"
24 25
#include "FlacCommon.hxx"
#include "FlacMetadata.hxx"
26
#include "OggCodec.hxx"
27
#include "fs/Path.hxx"
28
#include "fs/NarrowPath.hxx"
29
#include "util/Error.hxx"
30
#include "Log.hxx"
31

32 33 34 35
#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7
#error libFLAC is too old
#endif

36
static void flacPrintErroredState(FLAC__StreamDecoderState state)
37
{
Avuton Olrich's avatar
Avuton Olrich committed
38
	switch (state) {
39 40 41 42 43 44
	case FLAC__STREAM_DECODER_SEARCH_FOR_METADATA:
	case FLAC__STREAM_DECODER_READ_METADATA:
	case FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC:
	case FLAC__STREAM_DECODER_READ_FRAME:
	case FLAC__STREAM_DECODER_END_OF_STREAM:
		return;
45

46 47 48 49 50
	case FLAC__STREAM_DECODER_OGG_ERROR:
	case FLAC__STREAM_DECODER_SEEK_ERROR:
	case FLAC__STREAM_DECODER_ABORTED:
	case FLAC__STREAM_DECODER_MEMORY_ALLOCATION_ERROR:
	case FLAC__STREAM_DECODER_UNINITIALIZED:
51
		break;
Warren Dukes's avatar
Warren Dukes committed
52
	}
53

54
	LogError(flac_domain, FLAC__StreamDecoderStateString[state]);
Warren Dukes's avatar
Warren Dukes committed
55 56
}

57
static void flacMetadata(gcc_unused const FLAC__StreamDecoder * dec,
Avuton Olrich's avatar
Avuton Olrich committed
58
			 const FLAC__StreamMetadata * block, void *vdata)
59
{
60 61
	auto &fd = *(FlacDecoder *)vdata;
	fd.OnMetadata(*block);
Warren Dukes's avatar
Warren Dukes committed
62 63
}

Max Kellermann's avatar
Max Kellermann committed
64
static FLAC__StreamDecoderWriteStatus
65
flac_write_cb(const FLAC__StreamDecoder *dec, const FLAC__Frame *frame,
Max Kellermann's avatar
Max Kellermann committed
66
	      const FLAC__int32 *const buf[], void *vdata)
67
{
68 69
	auto &fd = *(FlacDecoder *)vdata;
	return fd.OnWrite(*frame, buf, fd.GetDeltaPosition(*dec));
Warren Dukes's avatar
Warren Dukes committed
70 71
}

72
static bool
73
flac_scan_file(Path path_fs,
74
	       const TagHandler &handler, void *handler_ctx)
Avuton Olrich's avatar
Avuton Olrich committed
75
{
76
	FlacMetadataChain chain;
77
	if (!chain.Read(NarrowPath(path_fs))) {
78 79 80
		FormatDebug(flac_domain,
			    "Failed to read FLAC tags: %s",
			    chain.GetStatusString());
81 82 83 84 85
		return false;
	}

	chain.Scan(handler, handler_ctx);
	return true;
Warren Dukes's avatar
Warren Dukes committed
86 87
}

88
static bool
89
flac_scan_stream(InputStream &is,
90
		 const TagHandler &handler, void *handler_ctx)
91
{
92
	FlacMetadataChain chain;
93
	if (!chain.Read(is)) {
94 95 96
		FormatDebug(flac_domain,
			    "Failed to read FLAC tags: %s",
			    chain.GetStatusString());
97 98 99 100 101 102 103
		return false;
	}

	chain.Scan(handler, handler_ctx);
	return true;
}

104 105 106
/**
 * Some glue code around FLAC__stream_decoder_new().
 */
107
static FlacStreamDecoder
108 109
flac_decoder_new(void)
{
110 111
	FlacStreamDecoder sd;
	if(!FLAC__stream_decoder_set_metadata_respond(sd.get(), FLAC__METADATA_TYPE_VORBIS_COMMENT))
112 113
		LogDebug(flac_domain,
			 "FLAC__stream_decoder_set_metadata_respond() has failed");
114 115 116 117

	return sd;
}

118
static bool
119
flac_decoder_initialize(FlacDecoder *data, FLAC__StreamDecoder *sd)
120 121
{
	if (!FLAC__stream_decoder_process_until_end_of_metadata(sd)) {
122 123
		if (FLAC__stream_decoder_get_state(sd) != FLAC__STREAM_DECODER_END_OF_STREAM)
			LogWarning(flac_domain, "problem reading metadata");
124 125 126
		return false;
	}

127
	if (data->initialized) {
128 129
		/* done */
		return true;
130
	}
131

132
	if (data->GetInputStream().IsSeekable())
133 134 135 136 137 138 139
		/* allow the workaround below only for nonseekable
		   streams*/
		return false;

	/* no stream_info packet found; try to initialize the decoder
	   from the first frame header */
	FLAC__stream_decoder_process_single(sd);
140
	return data->initialized;
141 142
}

143
static void
144
flac_decoder_loop(FlacDecoder *data, FLAC__StreamDecoder *flac_dec)
145
{
146
	Decoder &decoder = *data->GetDecoder();
147 148

	while (true) {
149
		DecoderCommand cmd;
Max Kellermann's avatar
Max Kellermann committed
150
		if (!data->tag.IsEmpty()) {
151
			cmd = decoder_tag(decoder, data->GetInputStream(),
Max Kellermann's avatar
Max Kellermann committed
152 153
					  std::move(data->tag));
			data->tag.Clear();
154 155 156
		} else
			cmd = decoder_get_command(decoder);

157
		if (cmd == DecoderCommand::SEEK) {
158
			FLAC__uint64 seek_sample =
159
				decoder_seek_where_frame(decoder);
160
			if (FLAC__stream_decoder_seek_absolute(flac_dec, seek_sample)) {
161 162 163 164
				data->position = 0;
				decoder_command_finished(decoder);
			} else
				decoder_seek_error(decoder);
165
		} else if (cmd == DecoderCommand::STOP)
166 167
			break;

168 169 170 171 172 173 174 175 176 177 178 179 180
		switch (FLAC__stream_decoder_get_state(flac_dec)) {
		case FLAC__STREAM_DECODER_SEARCH_FOR_METADATA:
		case FLAC__STREAM_DECODER_READ_METADATA:
		case FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC:
		case FLAC__STREAM_DECODER_READ_FRAME:
			/* continue decoding */
			break;

		case FLAC__STREAM_DECODER_END_OF_STREAM:
			/* regular end of stream */
			return;

		case FLAC__STREAM_DECODER_SEEK_ERROR:
181 182 183 184 185 186
			/* try to recover from seek error */
			if (!FLAC__stream_decoder_flush(flac_dec)) {
				LogError(flac_domain, "FLAC__stream_decoder_flush() failed");
				return;
			}

187 188
			break;

189
		case FLAC__STREAM_DECODER_OGG_ERROR:
190 191 192 193 194 195 196 197 198 199 200
		case FLAC__STREAM_DECODER_ABORTED:
		case FLAC__STREAM_DECODER_MEMORY_ALLOCATION_ERROR:
			/* an error, fatal enough for us to abort the
			   decoder */
			return;

		case FLAC__STREAM_DECODER_UNINITIALIZED:
			/* we shouldn't see this, ever - bail out */
			return;
		}

201
		if (!FLAC__stream_decoder_process_single(flac_dec) &&
202
		    decoder_get_command(decoder) == DecoderCommand::NONE) {
203 204 205 206
			/* a failure that was not triggered by a
			   decoder command */
			flacPrintErroredState(FLAC__stream_decoder_get_state(flac_dec));
			break;
207 208 209 210
		}
	}
}

211
static FLAC__StreamDecoderInitStatus
212
stream_init_oggflac(FLAC__StreamDecoder *flac_dec, FlacDecoder *data)
213 214
{
	return FLAC__stream_decoder_init_ogg_stream(flac_dec,
215 216 217 218 219
						    FlacInput::Read,
						    FlacInput::Seek,
						    FlacInput::Tell,
						    FlacInput::Length,
						    FlacInput::Eof,
220 221
						    flac_write_cb,
						    flacMetadata,
222
						    FlacInput::Error,
223 224 225 226
						    data);
}

static FLAC__StreamDecoderInitStatus
227
stream_init_flac(FLAC__StreamDecoder *flac_dec, FlacDecoder *data)
228 229
{
	return FLAC__stream_decoder_init_stream(flac_dec,
230 231 232 233 234
						FlacInput::Read,
						FlacInput::Seek,
						FlacInput::Tell,
						FlacInput::Length,
						FlacInput::Eof,
235
						flac_write_cb,
236
						flacMetadata,
237
						FlacInput::Error,
238 239 240 241
						data);
}

static FLAC__StreamDecoderInitStatus
242
stream_init(FLAC__StreamDecoder *flac_dec, FlacDecoder *data, bool is_ogg)
243 244 245 246 247 248
{
	return is_ogg
		? stream_init_oggflac(flac_dec, data)
		: stream_init_flac(flac_dec, data);
}

249
static bool
250
FlacInitAndDecode(FlacDecoder &data, FLAC__StreamDecoder *sd, bool is_ogg)
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
{
	auto init_status = stream_init(sd, &data, is_ogg);
	if (init_status != FLAC__STREAM_DECODER_INIT_STATUS_OK) {
		LogWarning(flac_domain,
			   FLAC__StreamDecoderInitStatusString[init_status]);
		return false;
	}

	bool result = flac_decoder_initialize(&data, sd);
	if (result)
		flac_decoder_loop(&data, sd);

	FLAC__stream_decoder_finish(sd);
	return result;
}

267
static void
268
flac_decode_internal(Decoder &decoder,
269
		     InputStream &input_stream,
270
		     bool is_ogg)
271
{
272 273
	auto flac_dec = flac_decoder_new();
	if (!flac_dec)
274
		return;
275

276
	FlacDecoder data(decoder, input_stream);
277

278
	FlacInitAndDecode(data, flac_dec.get(), is_ogg);
279 280
}

281
static void
282
flac_decode(Decoder &decoder, InputStream &input_stream)
283
{
Max Kellermann's avatar
Max Kellermann committed
284
	flac_decode_internal(decoder, input_stream, false);
285 286
}

287
static bool
288
oggflac_init(gcc_unused const ConfigBlock &block)
289 290 291 292
{
	return !!FLAC_API_SUPPORTS_OGG_FLAC;
}

293
static bool
294
oggflac_scan_file(Path path_fs,
295
		  const TagHandler &handler, void *handler_ctx)
296
{
297
	FlacMetadataChain chain;
298
	if (!chain.ReadOgg(NarrowPath(path_fs))) {
299 300 301
		FormatDebug(flac_domain,
			    "Failed to read OggFLAC tags: %s",
			    chain.GetStatusString());
302
		return false;
303 304
	}

305
	chain.Scan(handler, handler_ctx);
306
	return true;
307 308
}

309
static bool
310
oggflac_scan_stream(InputStream &is,
311
		    const TagHandler &handler, void *handler_ctx)
312
{
313
	FlacMetadataChain chain;
314
	if (!chain.ReadOgg(is)) {
315 316 317
		FormatDebug(flac_domain,
			    "Failed to read OggFLAC tags: %s",
			    chain.GetStatusString());
318 319 320 321 322 323 324
		return false;
	}

	chain.Scan(handler, handler_ctx);
	return true;
}

325
static void
326
oggflac_decode(Decoder &decoder, InputStream &input_stream)
327
{
328
	if (ogg_codec_detect(&decoder, input_stream) != OGG_CODEC_FLAC)
329
		return;
330

331
	/* rewind the stream, because ogg_codec_detect() has
332
	   moved it */
333
	input_stream.LockRewind(IgnoreError());
334

Max Kellermann's avatar
Max Kellermann committed
335
	flac_decode_internal(decoder, input_stream, true);
336 337
}

338
static const char *const oggflac_suffixes[] = { "ogg", "oga", nullptr };
339 340 341
static const char *const oggflac_mime_types[] = {
	"application/ogg",
	"application/x-ogg",
342 343 344
	"audio/ogg",
	"audio/x-flac+ogg",
	"audio/x-ogg",
345
	nullptr
346
};
347

348
const struct DecoderPlugin oggflac_decoder_plugin = {
349 350 351 352 353 354
	"oggflac",
	oggflac_init,
	nullptr,
	oggflac_decode,
	nullptr,
	oggflac_scan_file,
355
	oggflac_scan_stream,
356 357 358
	nullptr,
	oggflac_suffixes,
	oggflac_mime_types,
359
};
360

361
static const char *const flac_suffixes[] = { "flac", nullptr };
362
static const char *const flac_mime_types[] = {
363 364 365 366
	"application/flac",
	"application/x-flac",
	"audio/flac",
	"audio/x-flac",
367
	nullptr
368
};
Warren Dukes's avatar
Warren Dukes committed
369

370
const struct DecoderPlugin flac_decoder_plugin = {
371 372 373 374 375 376
	"flac",
	nullptr,
	nullptr,
	flac_decode,
	nullptr,
	flac_scan_file,
377
	flac_scan_stream,
378 379 380
	nullptr,
	flac_suffixes,
	flac_mime_types,
Warren Dukes's avatar
Warren Dukes committed
381
};