FlacDecoderPlugin.cxx 9.73 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 "FlacDecoderPlugin.h"
21
#include "FlacStreamDecoder.hxx"
22
#include "FlacDomain.hxx"
23
#include "FlacCommon.hxx"
24
#include "lib/xiph/FlacMetadataChain.hxx"
25
#include "OggCodec.hxx"
26
#include "input/InputStream.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
}

Rosen Penev's avatar
Rosen Penev committed
56
static void flacMetadata([[maybe_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, TagHandler &handler) noexcept
Avuton Olrich's avatar
Avuton Olrich committed
73
{
74
	FlacMetadataChain chain;
75
	if (!chain.Read(NarrowPath(path_fs))) {
76 77 78
		FormatDebug(flac_domain,
			    "Failed to read FLAC tags: %s",
			    chain.GetStatusString());
79 80 81
		return false;
	}

82
	chain.Scan(handler);
83
	return true;
Warren Dukes's avatar
Warren Dukes committed
84 85
}

86
static bool
87
flac_scan_stream(InputStream &is, TagHandler &handler) noexcept
88
{
89
	FlacMetadataChain chain;
90
	if (!chain.Read(is)) {
91 92 93
		FormatDebug(flac_domain,
			    "Failed to read FLAC tags: %s",
			    chain.GetStatusString());
94 95 96
		return false;
	}

97
	chain.Scan(handler);
98 99 100
	return true;
}

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

	return sd;
}

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

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

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

140 141 142
static DecoderCommand
FlacSubmitToClient(DecoderClient &client, FlacDecoder &d) noexcept
{
143
	if (d.tag.IsEmpty() && d.chunk.empty())
144 145
		return client.GetCommand();

146 147 148 149
	if (!d.tag.IsEmpty()) {
		auto cmd = client.SubmitTag(d.GetInputStream(),
					    std::move(d.tag));
		d.tag.Clear();
150 151 152 153
		if (cmd != DecoderCommand::NONE)
			return cmd;
	}

154
	if (!d.chunk.empty()) {
155 156 157 158 159 160 161 162 163 164
		auto cmd = client.SubmitData(d.GetInputStream(),
					     d.chunk.data,
					     d.chunk.size,
					     d.kbit_rate);
		d.chunk = nullptr;
		if (cmd != DecoderCommand::NONE)
			return cmd;
	}

	return DecoderCommand::NONE;
165 166
}

167
static void
168
flac_decoder_loop(FlacDecoder *data, FLAC__StreamDecoder *flac_dec)
169
{
170
	DecoderClient &client = *data->GetClient();
171 172

	while (true) {
173
		DecoderCommand cmd = FlacSubmitToClient(client, *data);
174

175
		if (cmd == DecoderCommand::SEEK) {
176
			FLAC__uint64 seek_sample = client.GetSeekFrame();
177
			if (FLAC__stream_decoder_seek_absolute(flac_dec, seek_sample)) {
178
				data->position = 0;
179
				client.CommandFinished();
180
			} else
181
				client.SeekError();
182 183 184 185 186

			/* FLAC__stream_decoder_seek_absolute()
			   decodes one frame and may have provided
			   data to be submitted to the client */
			continue;
187
		} else if (cmd == DecoderCommand::STOP)
188 189
			break;

190 191 192 193 194 195 196 197 198 199 200 201 202
		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:
203 204 205 206 207 208
			/* try to recover from seek error */
			if (!FLAC__stream_decoder_flush(flac_dec)) {
				LogError(flac_domain, "FLAC__stream_decoder_flush() failed");
				return;
			}

209 210
			break;

211
		case FLAC__STREAM_DECODER_OGG_ERROR:
212 213 214 215 216 217 218 219 220 221 222
		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;
		}

223
		if (!FLAC__stream_decoder_process_single(flac_dec) &&
224
		    client.GetCommand() == DecoderCommand::NONE) {
225 226 227 228
			/* a failure that was not triggered by a
			   decoder command */
			flacPrintErroredState(FLAC__stream_decoder_get_state(flac_dec));
			break;
229 230 231 232
		}
	}
}

233
static FLAC__StreamDecoderInitStatus
234
stream_init_oggflac(FLAC__StreamDecoder *flac_dec, FlacDecoder *data)
235 236
{
	return FLAC__stream_decoder_init_ogg_stream(flac_dec,
237 238 239 240 241
						    FlacInput::Read,
						    FlacInput::Seek,
						    FlacInput::Tell,
						    FlacInput::Length,
						    FlacInput::Eof,
242 243
						    flac_write_cb,
						    flacMetadata,
244
						    FlacInput::Error,
245 246 247 248
						    data);
}

static FLAC__StreamDecoderInitStatus
249
stream_init_flac(FLAC__StreamDecoder *flac_dec, FlacDecoder *data)
250 251
{
	return FLAC__stream_decoder_init_stream(flac_dec,
252 253 254 255 256
						FlacInput::Read,
						FlacInput::Seek,
						FlacInput::Tell,
						FlacInput::Length,
						FlacInput::Eof,
257
						flac_write_cb,
258
						flacMetadata,
259
						FlacInput::Error,
260 261 262 263
						data);
}

static FLAC__StreamDecoderInitStatus
264
stream_init(FLAC__StreamDecoder *flac_dec, FlacDecoder *data, bool is_ogg)
265 266 267 268 269 270
{
	return is_ogg
		? stream_init_oggflac(flac_dec, data)
		: stream_init_flac(flac_dec, data);
}

271
static bool
272
FlacInitAndDecode(FlacDecoder &data, FLAC__StreamDecoder *sd, bool is_ogg)
273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
{
	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;
}

289
static void
290
flac_decode_internal(DecoderClient &client,
291
		     InputStream &input_stream,
292
		     bool is_ogg)
293
{
294 295
	auto flac_dec = flac_decoder_new();
	if (!flac_dec)
296
		return;
297

298
	FlacDecoder data(client, input_stream);
299

300
	FlacInitAndDecode(data, flac_dec.get(), is_ogg);
301 302
}

303
static void
304
flac_decode(DecoderClient &client, InputStream &input_stream)
305
{
306
	flac_decode_internal(client, input_stream, false);
307 308
}

309
static bool
Rosen Penev's avatar
Rosen Penev committed
310
oggflac_init([[maybe_unused]] const ConfigBlock &block)
311 312 313 314
{
	return !!FLAC_API_SUPPORTS_OGG_FLAC;
}

315
static bool
316
oggflac_scan_file(Path path_fs, TagHandler &handler) noexcept
317
{
318
	FlacMetadataChain chain;
319
	if (!chain.ReadOgg(NarrowPath(path_fs))) {
320 321 322
		FormatDebug(flac_domain,
			    "Failed to read OggFLAC tags: %s",
			    chain.GetStatusString());
323
		return false;
324 325
	}

326
	chain.Scan(handler);
327
	return true;
328 329
}

330
static bool
331
oggflac_scan_stream(InputStream &is, TagHandler &handler) noexcept
332
{
333
	FlacMetadataChain chain;
334
	if (!chain.ReadOgg(is)) {
335 336 337
		FormatDebug(flac_domain,
			    "Failed to read OggFLAC tags: %s",
			    chain.GetStatusString());
338 339 340
		return false;
	}

341
	chain.Scan(handler);
342 343 344
	return true;
}

345
static void
346
oggflac_decode(DecoderClient &client, InputStream &input_stream)
347
{
348
	if (ogg_codec_detect(&client, input_stream) != OGG_CODEC_FLAC)
349
		return;
350

351
	/* rewind the stream, because ogg_codec_detect() has
352
	   moved it */
353 354
	try {
		input_stream.LockRewind();
355
	} catch (...) {
356
	}
357

358
	flac_decode_internal(client, input_stream, true);
359 360
}

361
static const char *const oggflac_suffixes[] = { "ogg", "oga", nullptr };
362 363 364
static const char *const oggflac_mime_types[] = {
	"application/ogg",
	"application/x-ogg",
365 366 367
	"audio/ogg",
	"audio/x-flac+ogg",
	"audio/x-ogg",
368
	nullptr
369
};
370

371 372 373 374 375 376
constexpr DecoderPlugin oggflac_decoder_plugin =
	DecoderPlugin("oggflac", oggflac_decode, oggflac_scan_stream,
		      nullptr, oggflac_scan_file)
	.WithInit(oggflac_init)
	.WithSuffixes(oggflac_suffixes)
	.WithMimeTypes(oggflac_mime_types);
377

378
static const char *const flac_suffixes[] = { "flac", nullptr };
379
static const char *const flac_mime_types[] = {
380 381 382 383
	"application/flac",
	"application/x-flac",
	"audio/flac",
	"audio/x-flac",
384
	nullptr
385
};
Warren Dukes's avatar
Warren Dukes committed
386

387 388 389 390 391
constexpr DecoderPlugin flac_decoder_plugin =
	DecoderPlugin("flac", flac_decode, flac_scan_stream,
		      nullptr, flac_scan_file)
	.WithSuffixes(flac_suffixes)
	.WithMimeTypes(flac_mime_types);