WavpackDecoderPlugin.cxx 13.2 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2021 The Music Player Daemon Project
3
 * http://www.musicpd.org
Max Kellermann's avatar
Max Kellermann 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.
18 19
 */

20
#include "config.h"
21
#include "WavpackDecoderPlugin.hxx"
22
#include "../DecoderAPI.hxx"
Max Kellermann's avatar
Max Kellermann committed
23
#include "input/InputStream.hxx"
24
#include "pcm/CheckAudioFormat.hxx"
25
#include "tag/Handler.hxx"
26
#include "fs/Path.hxx"
27
#include "util/Alloc.hxx"
28
#include "util/Math.hxx"
29
#include "util/ScopeExit.hxx"
30
#include "util/RuntimeError.hxx"
31 32

#include <wavpack/wavpack.h>
Max Kellermann's avatar
Max Kellermann committed
33

34
#include <algorithm>
35 36
#include <cassert>
#include <cstdlib>
37
#include <iterator>
38
#include <memory>
39

40 41
#define ERRORLEN 80

42 43
#ifdef OPEN_DSD_AS_PCM
/* libWavPack supports DSD since version 5 */
44 45 46
  #ifdef ENABLE_DSD
static constexpr int OPEN_DSD_FLAG = OPEN_DSD_NATIVE;
  #else
47
static constexpr int OPEN_DSD_FLAG = OPEN_DSD_AS_PCM;
48
  #endif
49 50 51 52 53
#else
/* no DSD support in this libWavPack version */
static constexpr int OPEN_DSD_FLAG = 0;
#endif

54 55 56 57 58 59 60 61 62 63 64 65 66
static WavpackContext *
WavpackOpenInput(Path path, int flags, int norm_offset)
{
	char error[ERRORLEN];
	auto *wpc = WavpackOpenFileInput(path.c_str(), error,
					 flags, norm_offset);
	if (wpc == nullptr)
		throw FormatRuntimeError("failed to open WavPack file \"%s\": %s",
					 path.c_str(), error);

	return wpc;
}

67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
#ifdef OPEN_DSD_AS_PCM

static WavpackContext *
WavpackOpenInput(WavpackStreamReader64 *reader, void *wv_id, void *wvc_id,
		 int flags, int norm_offset)
{
	char error[ERRORLEN];
	auto *wpc = WavpackOpenFileInputEx64(reader, wv_id, wvc_id, error,
					   flags, norm_offset);
	if (wpc == nullptr)
		throw FormatRuntimeError("failed to open WavPack stream: %s",
					 error);

	return wpc;
}

#else

85 86 87 88 89 90 91 92 93 94 95 96 97 98
static WavpackContext *
WavpackOpenInput(WavpackStreamReader *reader, void *wv_id, void *wvc_id,
		 int flags, int norm_offset)
{
	char error[ERRORLEN];
	auto *wpc = WavpackOpenFileInputEx(reader, wv_id, wvc_id, error,
					   flags, norm_offset);
	if (wpc == nullptr)
		throw FormatRuntimeError("failed to open WavPack stream: %s",
					 error);

	return wpc;
}

99 100
#endif

101 102
gcc_pure
static SignedSongTime
103
GetDuration(WavpackContext *wpc) noexcept
104
{
105 106 107 108 109 110 111
#ifdef OPEN_DSD_AS_PCM
	/* libWavPack 5 */
	const auto n_samples = WavpackGetNumSamples64(wpc);
	if (n_samples == -1)
		/* unknown */
		return SignedSongTime::Negative();
#else
112
	const uint32_t n_samples = WavpackGetNumSamples(wpc);
113 114 115
	if (n_samples == uint32_t(-1))
		/* unknown */
		return SignedSongTime::Negative();
116
#endif
117

118 119 120 121
	return SongTime::FromScale<uint64_t>(n_samples,
					     WavpackGetSampleRate(wpc));
}

122
/*
123
 * Convert integer samples.
124
 */
125
template<typename T>
Max Kellermann's avatar
Max Kellermann committed
126
static void
127
format_samples_int(void *buffer, uint32_t count)
128
{
Max Kellermann's avatar
Max Kellermann committed
129
	auto *src = (int32_t *)buffer;
130
	T *dst = (T *)buffer;
131 132 133 134 135 136 137 138
	/*
	 * The asserts like the following one are because we do the
	 * formatting of samples within a single buffer. The size of
	 * the output samples never can be greater than the size of
	 * the input ones. Otherwise we would have an overflow.
	 */
	static_assert(sizeof(*dst) <= sizeof(*src), "Wrong size");

139
	/* pass through and align samples */
140
	std::copy_n(src, count, dst);
141 142 143
}

/*
144
 * No conversion necessary.
145
 */
146
static void
Rosen Penev's avatar
Rosen Penev committed
147
format_samples_nop([[maybe_unused]] void *buffer, [[maybe_unused]] uint32_t count)
148
{
149
	/* do nothing */
150 151
}

152 153 154
/**
 * Choose a MPD sample format from libwavpacks' number of bits.
 */
155
static SampleFormat
156 157 158 159 160
wavpack_bits_to_sample_format(bool is_float,
#if defined(OPEN_DSD_AS_PCM) && defined(ENABLE_DSD)
			      bool is_dsd,
#endif
			      int bytes_per_sample)
161 162
{
	if (is_float)
163
		return SampleFormat::FLOAT;
164

165 166 167 168 169
#if defined(OPEN_DSD_AS_PCM) && defined(ENABLE_DSD)
	if (is_dsd)
		return SampleFormat::DSD;
#endif

170 171
	switch (bytes_per_sample) {
	case 1:
172
		return SampleFormat::S8;
173 174

	case 2:
175
		return SampleFormat::S16;
176 177

	case 3:
178
		return SampleFormat::S24_P32;
179 180

	case 4:
181
		return SampleFormat::S32;
182 183

	default:
184
		return SampleFormat::UNDEFINED;
185 186 187
	}
}

188 189
static AudioFormat
CheckAudioFormat(WavpackContext *wpc)
190
{
191
	const bool is_float = (WavpackGetMode(wpc) & MODE_FLOAT) != 0;
192 193 194
#if defined(OPEN_DSD_AS_PCM) && defined(ENABLE_DSD)
	const bool is_dsd = (WavpackGetQualifyMode(wpc) & QMODE_DSD_AUDIO) != 0;
#endif
195
	SampleFormat sample_format =
196
		wavpack_bits_to_sample_format(is_float,
197 198 199
#if defined(OPEN_DSD_AS_PCM) && defined(ENABLE_DSD)
					      is_dsd,
#endif
200
					      WavpackGetBytesPerSample(wpc));
201

202 203 204 205 206 207 208 209 210 211 212 213 214
	return CheckAudioFormat(WavpackGetSampleRate(wpc),
				sample_format,
				WavpackGetReducedChannels(wpc));
}

/*
 * This does the main decoding thing.
 * Requires an already opened WavpackContext.
 */
static void
wavpack_decode(DecoderClient &client, WavpackContext *wpc, bool can_seek)
{
	const auto audio_format = CheckAudioFormat(wpc);
215

216
	auto *format_samples = format_samples_nop;
217
	if (audio_format.format == SampleFormat::DSD)
218
		format_samples = format_samples_int<uint8_t>;
219
	else if (audio_format.format != SampleFormat::FLOAT) {
220 221
		switch (WavpackGetBytesPerSample(wpc)) {
		case 1:
222
			format_samples = format_samples_int<int8_t>;
223 224 225
			break;

		case 2:
226
			format_samples = format_samples_int<int16_t>;
227 228 229
			break;
		}
	}
230

231
	client.Ready(audio_format, can_seek, GetDuration(wpc));
232 233

	const int output_sample_size = audio_format.GetFrameSize();
234

235
	/* wavpack gives us all kind of samples in a 32-bit space */
236
	int32_t chunk[1024];
237
	const uint32_t samples_requested = std::size(chunk) /
238
		audio_format.channels;
239

240
	DecoderCommand cmd = client.GetCommand();
241 242
	while (cmd != DecoderCommand::STOP) {
		if (cmd == DecoderCommand::SEEK) {
243
			if (can_seek) {
244
				auto where = client.GetSeekFrame();
245 246 247 248 249 250
#ifdef OPEN_DSD_AS_PCM
				bool success = WavpackSeekSample64(wpc, where);
#else
				bool success = WavpackSeekSample(wpc, where);
#endif
				if (!success) {
251
					/* seek errors are fatal */
252
					client.SeekError();
253
					break;
254
				}
255 256

				client.CommandFinished();
257
			} else {
258
				client.SeekError();
259 260 261
			}
		}

262 263
		uint32_t samples_got = WavpackUnpackSamples(wpc, chunk,
							    samples_requested);
264
		if (samples_got == 0)
265 266
			break;

267
		int bitrate = lround(WavpackGetInstantBitrate(wpc) / 1000);
268
		format_samples(chunk, samples_got * audio_format.channels);
269

270 271 272
		cmd = client.SubmitData(nullptr, chunk,
					samples_got * output_sample_size,
					bitrate);
273
	}
274 275 276
}

/*
277
 * #InputStream <=> WavpackStreamReader wrapper callbacks
278 279
 */

Laszlo Ashin's avatar
Laszlo Ashin committed
280
/* This struct is needed for per-stream last_byte storage. */
281
struct WavpackInput {
282
	DecoderClient *const client;
283
	InputStream &is;
Laszlo Ashin's avatar
Laszlo Ashin committed
284 285
	/* Needed for push_back_byte() */
	int last_byte;
286

287
	constexpr WavpackInput(DecoderClient *_client, InputStream &_is)
288
		:client(_client), is(_is), last_byte(EOF) {}
289 290

	int32_t ReadBytes(void *data, size_t bcount);
291

292
	[[nodiscard]] InputStream::offset_type GetPos() const {
293 294 295 296 297 298 299
		return is.GetOffset();
	}

	int SetPosAbs(InputStream::offset_type pos) {
		try {
			is.LockSeek(pos);
			return 0;
300
		} catch (...) {
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337
			return -1;
		}
	}

	int SetPosRel(InputStream::offset_type delta, int mode) {
		offset_type offset = delta;
		switch (mode) {
		case SEEK_SET:
			break;

		case SEEK_CUR:
			offset += is.GetOffset();
			break;

		case SEEK_END:
			if (!is.KnownSize())
				return -1;

			offset += is.GetSize();
			break;

		default:
			return -1;
		}

		return SetPosAbs(offset);
	}

	int PushBackByte(int c) {
		if (last_byte == EOF) {
			last_byte = c;
			return c;
		} else {
			return EOF;
		}
	}

338
	[[nodiscard]] InputStream::offset_type GetLength() const {
339 340 341 342 343 344
		if (!is.KnownSize())
			return 0;

		return is.GetSize();
	}

345
	[[nodiscard]] bool CanSeek() const {
346 347
		return is.IsSeekable();
	}
Max Kellermann's avatar
Max Kellermann committed
348
};
Laszlo Ashin's avatar
Laszlo Ashin committed
349

350
/**
351
 * Little wrapper for struct WavpackInput to cast from void *.
352
 */
353
static WavpackInput *
354 355 356
wpin(void *id)
{
	assert(id);
357
	return (WavpackInput *)id;
358 359
}

360
static int32_t
361
wavpack_input_read_bytes(void *id, void *data, int32_t bcount)
362 363 364 365 366 367
{
	return wpin(id)->ReadBytes(data, bcount);
}

int32_t
WavpackInput::ReadBytes(void *data, size_t bcount)
368
{
Max Kellermann's avatar
Max Kellermann committed
369
	auto *buf = (uint8_t *)data;
370 371
	int32_t i = 0;

372 373 374
	if (last_byte != EOF) {
		*buf++ = last_byte;
		last_byte = EOF;
375 376 377
		--bcount;
		++i;
	}
378 379 380 381

	/* wavpack fails if we return a partial read, so we just wait
	   until the buffer is full */
	while (bcount > 0) {
382
		size_t nbytes = decoder_read(client, is, buf, bcount);
383
		if (nbytes == 0) {
384 385
			/* EOF, error or a decoder command */
			break;
386
		}
387 388 389 390 391 392 393

		i += nbytes;
		bcount -= nbytes;
		buf += nbytes;
	}

	return i;
394 395
}

396 397 398 399 400
#ifdef OPEN_DSD_AS_PCM

static int64_t
wavpack_input_get_pos(void *id)
{
401
	const auto &wpi = *wpin(id);
402 403 404 405 406 407
	return wpi.GetPos();
}

static int
wavpack_input_set_pos_abs(void *id, int64_t pos)
{
408
	auto &wpi = *wpin(id);
409 410 411 412 413 414
	return wpi.SetPosAbs(pos);
}

static int
wavpack_input_set_pos_rel(void *id, int64_t delta, int mode)
{
415
	auto &wpi = *wpin(id);
416 417 418 419 420
	return wpi.SetPosRel(delta, mode);
}

#else

421
static uint32_t
422
wavpack_input_get_pos(void *id)
423
{
424
	const auto &wpi = *wpin(id);
425
	return wpi.GetPos();
426 427
}

428
static int
429
wavpack_input_set_pos_abs(void *id, uint32_t pos)
430
{
431
	auto &wpi = *wpin(id);
432
	return wpi.SetPosAbs(pos);
433 434
}

435
static int
436
wavpack_input_set_pos_rel(void *id, int32_t delta, int mode)
437
{
438
	auto &wpi = *wpin(id);
439
	return wpi.SetPosRel(delta, mode);
440 441
}

442 443
#endif

444
static int
445
wavpack_input_push_back_byte(void *id, int c)
446
{
447
	auto &wpi = *wpin(id);
448
	return wpi.PushBackByte(c);
449 450
}

451 452 453 454 455
#ifdef OPEN_DSD_AS_PCM

static int64_t
wavpack_input_get_length(void *id)
{
456
	const auto &wpi = *wpin(id);
457 458 459 460 461
	return wpi.GetLength();
}

#else

462
static uint32_t
463
wavpack_input_get_length(void *id)
464
{
465
	const auto &wpi = *wpin(id);
466
	return wpi.GetLength();
467 468
}

469 470
#endif

471
static int
472
wavpack_input_can_seek(void *id)
473
{
474
	const auto &wpi = *wpin(id);
475
	return wpi.CanSeek();
476 477
}

478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494
#ifdef OPEN_DSD_AS_PCM

static WavpackStreamReader64 mpd_is_reader = {
	wavpack_input_read_bytes,
	nullptr, /* write_bytes */
	wavpack_input_get_pos,
	wavpack_input_set_pos_abs,
	wavpack_input_set_pos_rel,
	wavpack_input_push_back_byte,
	wavpack_input_get_length,
	wavpack_input_can_seek,
	nullptr, /* truncate_here */
	nullptr, /* close */
};

#else

495
static WavpackStreamReader mpd_is_reader = {
496 497 498 499 500 501 502 503
	wavpack_input_read_bytes,
	wavpack_input_get_pos,
	wavpack_input_set_pos_abs,
	wavpack_input_set_pos_rel,
	wavpack_input_push_back_byte,
	wavpack_input_get_length,
	wavpack_input_can_seek,
	nullptr /* no need to write edited tags */
504 505
};

506 507
#endif

508
static InputStreamPtr
509
wavpack_open_wvc(DecoderClient &client, const char *uri)
510
{
511 512 513 514
	/*
	 * As we use dc->utf8url, this function will be bad for
	 * single files. utf8url is not absolute file path :/
	 */
515
	if (uri == nullptr)
516
		return nullptr;
517

518
	char *wvc_url = xstrcatdup(uri, "c");
519 520 521
	AtScopeExit(wvc_url) {
		free(wvc_url);
	};
522

523
	try {
524
		return client.OpenUri(uri);
525
	} catch (...) {
526 527
		return nullptr;
	}
528
}
Laszlo Ashin's avatar
Laszlo Ashin committed
529

530 531 532
/*
 * Decodes a stream.
 */
533
static void
534
wavpack_streamdecode(DecoderClient &client, InputStream &is)
535
{
536
	int open_flags = OPEN_DSD_FLAG | OPEN_NORMALIZE;
537
	bool can_seek = is.IsSeekable();
Laszlo Ashin's avatar
Laszlo Ashin committed
538

539
	std::unique_ptr<WavpackInput> wvc;
540
	auto is_wvc = wavpack_open_wvc(client, is.GetURI());
541
	if (is_wvc) {
Laszlo Ashin's avatar
Laszlo Ashin committed
542
		open_flags |= OPEN_WVC;
543
		can_seek &= is_wvc->IsSeekable();
Laszlo Ashin's avatar
Laszlo Ashin committed
544

545
		wvc = std::make_unique<WavpackInput>(&client, *is_wvc);
546
	}
547

548 549 550 551
	if (!can_seek) {
		open_flags |= OPEN_STREAMING;
	}

552
	WavpackInput isp(&client, is);
553

554 555
	auto *wpc = WavpackOpenInput(&mpd_is_reader, &isp, wvc.get(),
				     open_flags, 0);
556 557 558
	AtScopeExit(wpc) {
		WavpackCloseFile(wpc);
	};
559

560
	wavpack_decode(client, wpc, can_seek);
561 562 563
}

/*
Laszlo Ashin's avatar
Laszlo Ashin committed
564
 * Decodes a file.
565
 */
566
static void
567
wavpack_filedecode(DecoderClient &client, Path path_fs)
568
{
569 570 571
	auto *wpc = WavpackOpenInput(path_fs,
				     OPEN_DSD_FLAG | OPEN_NORMALIZE | OPEN_WVC,
				     0);
572 573 574 575
	AtScopeExit(wpc) {
		WavpackCloseFile(wpc);
	};

576
	wavpack_decode(client, wpc, true);
577 578
}

579 580 581 582 583 584 585 586 587 588 589 590 591
static void
Scan(WavpackContext *wpc,TagHandler &handler) noexcept
{
	try {
		handler.OnAudioFormat(CheckAudioFormat(wpc));
	} catch (...) {
	}

	const auto duration = GetDuration(wpc);
	if (!duration.IsNegative())
		handler.OnDuration(SongTime(duration));
}

592 593 594 595
/*
 * Reads metainfo from the specified file.
 */
static bool
596
wavpack_scan_file(Path path_fs, TagHandler &handler) noexcept
597
{
598 599 600 601 602 603 604 605
	WavpackContext *wpc;

	try {
		wpc = WavpackOpenInput(path_fs, OPEN_DSD_FLAG, 0);
	} catch (...) {
		return false;
	}

606 607 608 609
	AtScopeExit(wpc) {
		WavpackCloseFile(wpc);
	};

610
	Scan(wpc, handler);
611 612 613
	return true;
}

614
static bool
615
wavpack_scan_stream(InputStream &is, TagHandler &handler)
616 617
{
	WavpackInput isp(nullptr, is);
618 619 620 621 622 623 624 625 626

	WavpackContext *wpc;
	try {
		wpc = WavpackOpenInput(&mpd_is_reader, &isp, nullptr,
					     OPEN_DSD_FLAG, 0);
	} catch (...) {
		return false;
	}

627 628 629 630
	AtScopeExit(wpc) {
		WavpackCloseFile(wpc);
	};

631
	Scan(wpc, handler);
632 633 634
	return true;
}

635 636
static char const *const wavpack_suffixes[] = {
	"wv",
637
	nullptr
638 639 640 641
};

static char const *const wavpack_mime_types[] = {
	"audio/x-wavpack",
642
	nullptr
643
};
644

645 646 647 648 649
constexpr DecoderPlugin wavpack_decoder_plugin =
	DecoderPlugin("wavpack", wavpack_streamdecode, wavpack_scan_stream,
		      wavpack_filedecode, wavpack_scan_file)
	.WithSuffixes(wavpack_suffixes)
	.WithMimeTypes(wavpack_mime_types);