CdioParanoiaInputPlugin.cxx 8.55 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2017 The Music Player Daemon Project
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
 * http://www.musicpd.org
 *
 * 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.
 *
 * 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.
 */

/**
21 22
 * CD-Audio handling (requires libcdio_paranoia)
 */
23 24

#include "config.h"
25
#include "CdioParanoiaInputPlugin.hxx"
Max Kellermann's avatar
Max Kellermann committed
26 27
#include "../InputStream.hxx"
#include "../InputPlugin.hxx"
28
#include "util/TruncateString.hxx"
29
#include "util/StringCompare.hxx"
30
#include "util/RuntimeError.hxx"
31
#include "util/Domain.hxx"
32
#include "system/ByteOrder.hxx"
33
#include "fs/AllocatedPath.hxx"
34
#include "Log.hxx"
35
#include "config/Block.hxx"
36
#include "config/Domain.hxx"
37 38 39 40 41 42 43

#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>

44
#ifdef HAVE_CDIO_PARANOIA_PARANOIA_H
45
#include <cdio/paranoia/paranoia.h>
46
#else
47
#include <cdio/paranoia.h>
48 49
#endif

50 51
#include <cdio/cd_types.h>

52
class CdioParanoiaInputStream final : public InputStream {
53 54 55
	cdrom_drive_t *const drv;
	CdIo_t *const cdio;
	cdrom_paranoia_t *const para;
56

57
	const lsn_t lsn_from, lsn_to;
58 59 60 61
	int lsn_relofs;

	char buffer[CDIO_CD_FRAMESIZE_RAW];
	int buffer_lsn;
62

63
 public:
64
	CdioParanoiaInputStream(const char *_uri, Mutex &_mutex,
65 66 67
				cdrom_drive_t *_drv, CdIo_t *_cdio,
				bool reverse_endian,
				lsn_t _lsn_from, lsn_t _lsn_to)
68
		:InputStream(_uri, _mutex),
69 70
		 drv(_drv), cdio(_cdio), para(cdio_paranoia_init(drv)),
		 lsn_from(_lsn_from), lsn_to(_lsn_to),
71
		 lsn_relofs(0),
72
		 buffer_lsn(-1)
73
	{
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
		/* Set reading mode for full paranoia, but allow
		   skipping sectors. */
		paranoia_modeset(para,
				 PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP);

		/* seek to beginning of the track */
		cdio_paranoia_seek(para, lsn_from, SEEK_SET);

		seekable = true;
		size = (lsn_to - lsn_from + 1) * CDIO_CD_FRAMESIZE_RAW;

		/* hack to make MPD select the "pcm" decoder plugin */
		SetMimeType(reverse_endian
			    ? "audio/x-mpd-cdda-pcm-reverse"
			    : "audio/x-mpd-cdda-pcm");
		SetReady();
90 91 92
	}

	~CdioParanoiaInputStream() {
93 94 95
		cdio_paranoia_free(para);
		cdio_cddap_close_no_free_cdio(drv);
		cdio_destroy(cdio);
96
	}
97 98

	/* virtual methods from InputStream */
99
	bool IsEOF() noexcept override;
100 101
	size_t Read(void *ptr, size_t size) override;
	void Seek(offset_type offset) override;
102 103
};

104
static constexpr Domain cdio_domain("cdio");
105

106 107
static bool default_reverse_endian;

108
static void
109
input_cdio_init(EventLoop &, const ConfigBlock &block)
110
{
111
	const char *value = block.GetBlockValue("default_byte_order");
112 113 114 115 116
	if (value != nullptr) {
		if (strcmp(value, "little_endian") == 0)
			default_reverse_endian = IsBigEndian();
		else if (strcmp(value, "big_endian") == 0)
			default_reverse_endian = IsLittleEndian();
117 118 119
		else
			throw FormatRuntimeError("Unrecognized 'default_byte_order' setting: %s",
						 value);
120 121 122
	}
}

123
struct cdio_uri {
124 125 126 127 128
	char device[64];
	int track;
};

static bool
129
parse_cdio_uri(struct cdio_uri *dest, const char *src)
130
{
131
	if (!StringStartsWith(src, "cdda://"))
132 133 134 135 136 137 138 139 140 141 142 143
		return false;

	src += 7;

	if (*src == 0) {
		/* play the whole CD in the default drive */
		dest->device[0] = 0;
		dest->track = -1;
		return true;
	}

	const char *slash = strrchr(src, '/');
144
	if (slash == nullptr) {
145
		/* play the whole CD in the specified drive */
146
		CopyTruncateString(dest->device, src, sizeof(dest->device));
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
		dest->track = -1;
		return true;
	}

	size_t device_length = slash - src;
	if (device_length >= sizeof(dest->device))
		device_length = sizeof(dest->device) - 1;

	memcpy(dest->device, src, device_length);
	dest->device[device_length] = 0;

	const char *track = slash + 1;

	char *endptr;
	dest->track = strtoul(track, &endptr, 10);
162 163
	if (*endptr != 0)
		throw std::runtime_error("Malformed track number");
164 165 166 167 168 169 170 171

	if (endptr == track)
		/* play the whole CD */
		dest->track = -1;

	return true;
}

172
static AllocatedPath
173
cdio_detect_device(void)
174
{
175 176 177
	char **devices = cdio_get_devices_with_cap(nullptr, CDIO_FS_AUDIO,
						   false);
	if (devices == nullptr)
178
		return nullptr;
179

180
	AllocatedPath path = AllocatedPath::FromFS(devices[0]);
181
	cdio_free_device_list(devices);
182
	return path;
183 184
}

185
static InputStreamPtr
186
input_cdio_open(const char *uri,
187
		Mutex &mutex)
188
{
189
	struct cdio_uri parsed_uri;
190
	if (!parse_cdio_uri(&parsed_uri, uri))
191
		return nullptr;
192 193

	/* get list of CD's supporting CD-DA */
194 195
	const AllocatedPath device = parsed_uri.device[0] != 0
		? AllocatedPath::FromFS(parsed_uri.device)
196
		: cdio_detect_device();
197 198
	if (device.IsNull())
		throw std::runtime_error("Unable find or access a CD-ROM drive with an audio CD in it.");
199 200

	/* Found such a CD-ROM with a CD-DA loaded. Use the first drive in the list. */
201
	const auto cdio = cdio_open(device.c_str(), DRIVER_UNKNOWN);
202 203
	if (cdio == nullptr)
		throw std::runtime_error("Failed to open CD drive");
204

205 206 207
	const auto drv = cdio_cddap_identify_cdio(cdio, 1, nullptr);
	if (drv == nullptr) {
		cdio_destroy(cdio);
208
		throw std::runtime_error("Unable to identify audio CD disc.");
209 210
	}

211
	cdda_verbose_set(drv, CDDA_MESSAGE_FORGETIT, CDDA_MESSAGE_FORGETIT);
212

213 214 215
	if (0 != cdio_cddap_open(drv)) {
		cdio_cddap_close_no_free_cdio(drv);
		cdio_destroy(cdio);
216
		throw std::runtime_error("Unable to open disc.");
217 218
	}

219
	bool reverse_endian;
220 221
	const int be = data_bigendianp(drv);
	switch (be) {
222
	case -1:
223
		LogDebug(cdio_domain, "drive returns unknown audio data");
224
		reverse_endian = default_reverse_endian;
225
		break;
226

227
	case 0:
228
		LogDebug(cdio_domain, "drive returns audio data Little Endian");
229
		reverse_endian = IsBigEndian();
230
		break;
231

232
	case 1:
233
		LogDebug(cdio_domain, "drive returns audio data Big Endian");
234
		reverse_endian = IsLittleEndian();
235
		break;
236

237
	default:
238 239
		cdio_cddap_close_no_free_cdio(drv);
		cdio_destroy(cdio);
240 241
		throw FormatRuntimeError("Drive returns unknown data type %d",
					 be);
242 243
	}

244
	lsn_t lsn_from, lsn_to;
245
	if (parsed_uri.track >= 0) {
246 247
		lsn_from = cdio_get_track_lsn(cdio, parsed_uri.track);
		lsn_to = cdio_get_track_last_lsn(cdio, parsed_uri.track);
248
	} else {
249 250
		lsn_from = 0;
		lsn_to = cdio_get_disc_last_lsn(cdio);
251
	}
252

253
	return std::make_unique<CdioParanoiaInputStream>(uri, mutex,
254 255 256
							 drv, cdio,
							 reverse_endian,
							 lsn_from, lsn_to);
257 258
}

259 260
void
CdioParanoiaInputStream::Seek(offset_type new_offset)
261
{
262 263 264
	if (new_offset > size)
		throw FormatRuntimeError("Invalid offset to seek %ld (%ld)",
					 (long int)new_offset, (long int)size);
265 266

	/* simple case */
267
	if (new_offset == offset)
268
		return;
269 270

	/* calculate current LSN */
271 272
	lsn_relofs = new_offset / CDIO_CD_FRAMESIZE_RAW;
	offset = new_offset;
273

274 275 276 277
	{
		const ScopeUnlock unlock(mutex);
		cdio_paranoia_seek(para, lsn_from + lsn_relofs, SEEK_SET);
	}
278 279
}

280
size_t
281
CdioParanoiaInputStream::Read(void *ptr, size_t length)
282 283 284 285 286 287 288 289 290 291 292 293
{
	size_t nbytes = 0;
	int diff;
	size_t len, maxwrite;
	int16_t *rbuf;
	char *s_err, *s_mess;
	char *wptr = (char *) ptr;

	while (length > 0) {


		/* end of track ? */
294
		if (lsn_from + lsn_relofs > lsn_to)
295 296 297
			break;

		//current sector was changed ?
298
		if (lsn_relofs != buffer_lsn) {
299 300
			const ScopeUnlock unlock(mutex);

301
			rbuf = cdio_paranoia_read(para, nullptr);
302

303
			s_err = cdda_errors(drv);
304
			if (s_err) {
305 306
				FormatError(cdio_domain,
					    "paranoia_read: %s", s_err);
307 308
				free(s_err);
			}
309
			s_mess = cdda_messages(drv);
310 311 312
			if (s_mess) {
				free(s_mess);
			}
313 314 315
			if (!rbuf)
				throw std::runtime_error("paranoia read error");

316
			//store current buffer
317 318
			memcpy(buffer, rbuf, CDIO_CD_FRAMESIZE_RAW);
			buffer_lsn = lsn_relofs;
319 320
		} else {
			//use cached sector
321
			rbuf = (int16_t *)buffer;
322 323 324
		}

		//correct offset
325
		diff = offset - lsn_relofs * CDIO_CD_FRAMESIZE_RAW;
326 327 328 329 330 331 332 333 334 335 336 337 338

		assert(diff >= 0 && diff < CDIO_CD_FRAMESIZE_RAW);

		maxwrite = CDIO_CD_FRAMESIZE_RAW - diff;  //# of bytes pending in current buffer
		len = (length < maxwrite? length : maxwrite);

		//skip diff bytes from this lsn
		memcpy(wptr, ((char*)rbuf) + diff, len);
		//update pointer
		wptr += len;
		nbytes += len;

		//update offset
339 340
		offset += len;
		lsn_relofs = offset / CDIO_CD_FRAMESIZE_RAW;
341 342 343 344 345 346 347
		//update length
		length -= len;
	}

	return nbytes;
}

348
bool
349
CdioParanoiaInputStream::IsEOF() noexcept
350
{
351
	return lsn_from + lsn_relofs > lsn_to;
352 353
}

354
const InputPlugin input_plugin_cdio_paranoia = {
355
	"cdio_paranoia",
356
	input_cdio_init,
357 358
	nullptr,
	input_cdio_open,
359
};