WavpackDecoderPlugin.cxx 13.2 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
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/ScopeExit.hxx"
29
#include "util/RuntimeError.hxx"
30 31

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

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

39 40
#define ERRORLEN 80

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

53 54 55 56 57 58 59 60 61 62 63 64 65
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;
}

66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
#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

84 85 86 87 88 89 90 91 92 93 94 95 96 97
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;
}

98 99
#endif

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

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

121
/*
122
 * Convert integer samples.
123
 */
124
template<typename T>
Max Kellermann's avatar
Max Kellermann committed
125
static void
126
format_samples_int(void *buffer, uint32_t count)
127
{
Max Kellermann's avatar
Max Kellermann committed
128
	auto *src = (int32_t *)buffer;
129
	T *dst = (T *)buffer;
130 131 132 133 134 135 136 137
	/*
	 * 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");

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

/*
143
 * No conversion necessary.
144
 */
145
static void
146
format_samples_nop(gcc_unused void *buffer, gcc_unused uint32_t count)
147
{
148
	/* do nothing */
149 150
}

151 152 153
/**
 * Choose a MPD sample format from libwavpacks' number of bits.
 */
154
static SampleFormat
155 156 157 158 159
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)
160 161
{
	if (is_float)
162
		return SampleFormat::FLOAT;
163

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

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

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

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

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

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

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

201 202 203 204 205 206 207 208 209 210 211 212 213
	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);
214

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

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

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

	const int output_sample_size = audio_format.GetFrameSize();
233

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

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

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

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

266 267
		int bitrate = (int)(WavpackGetInstantBitrate(wpc) / 1000 +
				    0.5);
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 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420
#ifdef OPEN_DSD_AS_PCM

static int64_t
wavpack_input_get_pos(void *id)
{
	WavpackInput &wpi = *wpin(id);
	return wpi.GetPos();
}

static int
wavpack_input_set_pos_abs(void *id, int64_t pos)
{
	WavpackInput &wpi = *wpin(id);
	return wpi.SetPosAbs(pos);
}

static int
wavpack_input_set_pos_rel(void *id, int64_t delta, int mode)
{
	WavpackInput &wpi = *wpin(id);
	return wpi.SetPosRel(delta, mode);
}

#else

421
static uint32_t
422
wavpack_input_get_pos(void *id)
423
{
424
	WavpackInput &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
	WavpackInput &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
	WavpackInput &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
	WavpackInput &wpi = *wpin(id);
448
	return wpi.PushBackByte(c);
449 450
}

451 452 453 454 455 456 457 458 459 460 461
#ifdef OPEN_DSD_AS_PCM

static int64_t
wavpack_input_get_length(void *id)
{
	WavpackInput &wpi = *wpin(id);
	return wpi.GetLength();
}

#else

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

469 470
#endif

471
static int
472
wavpack_input_can_seek(void *id)
473
{
474
	WavpackInput &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) noexcept
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);