FlacDecoderPlugin.cxx 9.27 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" /* 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 "Log.hxx"
30

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

35
static void flacPrintErroredState(FLAC__StreamDecoderState state)
36
{
Avuton Olrich's avatar
Avuton Olrich committed
37
	switch (state) {
38 39 40 41 42 43
	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;
44

45 46 47 48 49
	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:
50
		break;
Warren Dukes's avatar
Warren Dukes committed
51
	}
52

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

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

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

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

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

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

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

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

	return sd;
}

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

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

131
	if (data->GetInputStream().IsSeekable())
132 133 134 135 136 137 138
		/* 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);
139
	return data->initialized;
140 141
}

142
static void
143
flac_decoder_loop(FlacDecoder *data, FLAC__StreamDecoder *flac_dec)
144
{
145
	DecoderClient &client = *data->GetClient();
146 147

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

156
		if (cmd == DecoderCommand::SEEK) {
157
			FLAC__uint64 seek_sample = client.GetSeekFrame();
158
			if (FLAC__stream_decoder_seek_absolute(flac_dec, seek_sample)) {
159
				data->position = 0;
160
				client.CommandFinished();
161
			} else
162
				client.SeekError();
163
		} else if (cmd == DecoderCommand::STOP)
164 165
			break;

166 167 168 169 170 171 172 173 174 175 176 177 178
		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:
179 180 181 182 183 184
			/* try to recover from seek error */
			if (!FLAC__stream_decoder_flush(flac_dec)) {
				LogError(flac_domain, "FLAC__stream_decoder_flush() failed");
				return;
			}

185 186
			break;

187
		case FLAC__STREAM_DECODER_OGG_ERROR:
188 189 190 191 192 193 194 195 196 197 198
		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;
		}

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

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

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

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

247
static bool
248
FlacInitAndDecode(FlacDecoder &data, FLAC__StreamDecoder *sd, bool is_ogg)
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
{
	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;
}

265
static void
266
flac_decode_internal(DecoderClient &client,
267
		     InputStream &input_stream,
268
		     bool is_ogg)
269
{
270 271
	auto flac_dec = flac_decoder_new();
	if (!flac_dec)
272
		return;
273

274
	FlacDecoder data(client, input_stream);
275

276
	FlacInitAndDecode(data, flac_dec.get(), is_ogg);
277 278
}

279
static void
280
flac_decode(DecoderClient &client, InputStream &input_stream)
281
{
282
	flac_decode_internal(client, input_stream, false);
283 284
}

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

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

303
	chain.Scan(handler, handler_ctx);
304
	return true;
305 306
}

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

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

323
static void
324
oggflac_decode(DecoderClient &client, InputStream &input_stream)
325
{
326
	if (ogg_codec_detect(&client, input_stream) != OGG_CODEC_FLAC)
327
		return;
328

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

336
	flac_decode_internal(client, input_stream, true);
337 338
}

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

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

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

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