1/*
2 * Copyright 2006-2007, Haiku, Inc. All Rights Reserved.
3 * Distributed under the terms of the MIT license.
4 *
5 * Author:
6 *		DarkWyrm, bpmagic@columbus.rr.com
7 */
8
9
10#include "CDAudioDevice.h"
11#include "scsi.h"
12
13#include <Debug.h>
14#include <Directory.h>
15#include <Entry.h>
16#include <Path.h>
17#include <String.h>
18
19#include <errno.h>
20#include <stdio.h>
21#include <stdlib.h>
22#include <string.h>
23#include <unistd.h>
24
25
26struct ConvertedToc {
27	int32 min;
28	int32 sec;
29	int32 frame;
30};
31
32
33static int32
34cddb_sum(int n)
35{
36	char buf[12];
37	int32 ret = 0;
38
39	sprintf(buf, "%u", n);
40	for (const char *p = buf; *p != '\0'; p++)
41		ret += (*p - '0');
42	return ret;
43}
44
45
46//	#pragma mark -
47
48
49CDAudioData::CDAudioData(const int32 &id, const int32 &count,
50	const int32 &discLength)
51	:
52	fDiscId(id),
53	fTrackCount(count),
54	fLength(discLength)
55{
56}
57
58
59CDAudioData::CDAudioData(const CDAudioData &from)
60	:
61	fDiscId(from.fDiscId),
62	fTrackCount(from.fTrackCount),
63	fLength(from.fLength)
64{
65}
66
67
68CDAudioData &
69CDAudioData::operator=(const CDAudioData &from)
70{
71	fDiscId = from.fDiscId;
72	fTrackCount = from.fTrackCount;
73	fLength = from.fLength;
74	fFrameOffsets = from.fFrameOffsets;
75	return *this;
76}
77
78
79//	#pragma mark -
80
81
82CDAudioTime::CDAudioTime(const int32 min,const int32 &sec)
83	:
84	fMinutes(min),
85	fSeconds(sec)
86{
87}
88
89
90CDAudioTime::CDAudioTime(const CDAudioTime &from)
91	:
92	fMinutes(from.fMinutes),
93	fSeconds(from.fSeconds)
94{
95}
96
97
98CDAudioTime &
99CDAudioTime::operator=(const CDAudioTime &from)
100{
101	fMinutes = from.fMinutes;
102	fSeconds = from.fSeconds;
103	return *this;
104}
105
106
107CDAudioTime
108CDAudioTime::operator+(const CDAudioTime &from)
109{
110	CDAudioTime time;
111
112	time.fMinutes = fMinutes + from.fMinutes;
113	time.fSeconds = fSeconds + from.fSeconds;
114
115	while (time.fSeconds > 59) {
116		time.fMinutes++;
117		time.fSeconds -= 60;
118	}
119	return time;
120}
121
122
123CDAudioTime
124CDAudioTime::operator-(const CDAudioTime &from)
125{
126	CDAudioTime time;
127
128	int32 tsec = ((fMinutes * 60) + fSeconds) - ((from.fMinutes * 60)
129		+ from.fSeconds);
130	if (tsec < 0) {
131		time.fMinutes = 0;
132		time.fSeconds = 0;
133		return time;
134	}
135
136	time.fMinutes = tsec / 60;
137	time.fSeconds = tsec % 60;
138
139	return time;
140}
141
142
143//	#pragma mark -
144
145
146CDAudioDevice::CDAudioDevice()
147{
148	_FindDrives("/dev/disk");
149	if (CountDrives() > 0)
150		SetDrive(0);
151}
152
153
154CDAudioDevice::~CDAudioDevice()
155{
156	for (int32 i = 0; i < fDriveList.CountItems(); i++)
157		delete (BString*) fDriveList.ItemAt(i);
158}
159
160
161
162//!	This plays only one track - the track specified
163bool
164CDAudioDevice::Play(const int16 &track)
165{
166	if (GetState() == kNoCD) {
167		// no CD available, bail out
168		ioctl(fFileHandle, B_LOAD_MEDIA, 0, 0);
169		return false;
170	}
171
172	scsi_play_track playtrack;
173
174	playtrack.start_track = track;
175	playtrack.start_index = 1;
176	playtrack.end_track = track;
177	playtrack.end_index = 1;
178
179	status_t result = ioctl(fFileHandle, B_SCSI_PLAY_TRACK, &playtrack);
180	if (result != B_OK) {
181		printf("Couldn't play track: %s\n", strerror(errno));
182		return false;
183	}
184
185	return true;
186}
187
188
189bool
190CDAudioDevice::Pause()
191{
192	status_t result = ioctl(fFileHandle, B_SCSI_PAUSE_AUDIO);
193	if (result != B_OK) {
194		printf("Couldn't pause track: %s\n", strerror(errno));
195		return false;
196	}
197	return true;
198}
199
200
201bool
202CDAudioDevice::Resume()
203{
204	CDState state = GetState();
205	if (state == kNoCD) {
206		// no CD available, bail out
207		ioctl(fFileHandle, B_LOAD_MEDIA, 0, 0);
208		return false;
209	} else {
210		if (state == kStopped)
211			return Play(0);
212	}
213
214	status_t result = ioctl(fFileHandle, B_SCSI_RESUME_AUDIO);
215	if (result != B_OK) {
216		printf("Couldn't resume track: %s\n", strerror(errno));
217		return false;
218	}
219	return true;
220}
221
222
223bool
224CDAudioDevice::Stop()
225{
226	status_t result = ioctl(fFileHandle, B_SCSI_STOP_AUDIO);
227	if (result != B_OK) {
228		printf("Couldn't stop CD: %s\n", strerror(errno));
229		return false;
230	}
231	return true;
232}
233
234
235//!	Open or close the CD tray
236bool
237CDAudioDevice::Eject()
238{
239	status_t media_status = B_DEV_NO_MEDIA;
240
241	// get the status first
242	ioctl(fFileHandle, B_GET_MEDIA_STATUS, &media_status, sizeof(media_status));
243
244	// if door open, load the media, else eject the CD
245	status_t result = ioctl(fFileHandle,
246		media_status == B_DEV_DOOR_OPEN ? B_LOAD_MEDIA : B_EJECT_DEVICE);
247
248	if (result != B_OK) {
249		printf("Couldn't eject CD: %s\n", strerror(errno));
250		return false;
251	}
252	return true;
253}
254
255
256bool
257CDAudioDevice::StartFastFwd()
258{
259	scsi_scan scan;
260	scan.direction = 1;
261	scan.speed = 1;
262	status_t result = ioctl(fFileHandle, B_SCSI_SCAN, &scan);
263	if (result != B_OK)	{
264		printf("Couldn't fast forward: %s\n", strerror(errno));
265		return false;
266	}
267	return true;
268}
269
270
271bool
272CDAudioDevice::StopFastFwd()
273{
274	scsi_scan scan;
275	scan.direction = 0;
276	scan.speed = 1;
277	status_t result = ioctl(fFileHandle, B_SCSI_SCAN, &scan);
278	if (result != B_OK)	{
279		printf("Couldn't stop fast forwarding: %s\n", strerror(errno));
280		return false;
281	}
282	return true;
283}
284
285
286bool
287CDAudioDevice::StartRewind()
288{
289	scsi_scan scan;
290	scan.direction = -1;
291	scan.speed = 1;
292	status_t result = ioctl(fFileHandle, B_SCSI_SCAN, &scan);
293	if (result != B_OK)	{
294		printf("Couldn't rewind: %s\n", strerror(errno));
295		return false;
296	}
297	return true;
298}
299
300
301bool
302CDAudioDevice::StopRewind()
303{
304	scsi_scan scan;
305	scan.direction = 0;
306	scan.speed = 1;
307	status_t result = ioctl(fFileHandle, B_SCSI_SCAN, &scan);
308	if (result != B_OK)	{
309		printf("Couldn't stop rewinding: %s\n", strerror(errno));
310		return false;
311	}
312	return true;
313}
314
315
316bool
317CDAudioDevice::SetVolume(uint8 value)
318{
319	scsi_volume vol;
320
321	// change only port0's volume
322	vol.flags = 2;
323	vol.port0_volume = value;
324
325	status_t result = ioctl(fFileHandle, B_SCSI_SET_VOLUME, &vol);
326	if (result != B_OK)	{
327		printf("Couldn't set volume: %s\n", strerror(errno));
328		return false;
329	}
330	return true;
331}
332
333
334uint8
335CDAudioDevice::GetVolume()
336{
337	scsi_volume vol;
338	ioctl(fFileHandle, B_SCSI_GET_VOLUME, &vol);
339	return vol.port0_volume;
340}
341
342
343//! Check the current CD play state
344CDState
345CDAudioDevice::GetState()
346{
347	scsi_position pos;
348	status_t media_status = B_DEV_NO_MEDIA;
349
350	ioctl(fFileHandle, B_GET_MEDIA_STATUS, &media_status, sizeof(media_status));
351	if (media_status != B_OK)
352		return kNoCD;
353
354	status_t result = ioctl(fFileHandle, B_SCSI_GET_POSITION, &pos);
355	if (result != B_OK)
356		return kNoCD;
357	else if ((!pos.position[1]) || (pos.position[1] >= 0x13) ||
358		   ((pos.position[1] == 0x12) && (!pos.position[6])))
359		return kStopped;
360	else if (pos.position[1] == 0x11)
361		return kPlaying;
362	else
363		return kPaused;
364}
365
366
367int16
368CDAudioDevice::CountTracks()
369{
370	scsi_toc toc;
371	status_t result = ioctl(fFileHandle, B_SCSI_GET_TOC, &toc);
372
373	if (result != B_OK)
374		return -1;
375
376	return toc.toc_data[3];
377}
378
379
380//! Get the 0-based index of the current track
381int16
382CDAudioDevice::GetTrack()
383{
384	scsi_position pos;
385
386	status_t media_status = B_DEV_NO_MEDIA;
387
388	ioctl(fFileHandle, B_GET_MEDIA_STATUS, &media_status, sizeof(media_status));
389	if (media_status != B_OK)
390		return -1;
391
392	status_t result = ioctl(fFileHandle, B_SCSI_GET_POSITION, &pos);
393	if (result != B_OK)
394		return -1;
395
396	if (!pos.position[1] || pos.position[1] >= 0x13
397		|| (pos.position[1] == 0x12 && !pos.position[6]))
398		return 0;
399	else
400		return pos.position[6];
401}
402
403
404uint8
405CDAudioDevice::CountDrives()
406{
407	return fDriveList.CountItems();
408}
409
410
411bool
412CDAudioDevice::SetDrive(const int32 &drive)
413{
414	BString *path = (BString*) fDriveList.ItemAt(drive);
415
416	if (!path)
417		return false;
418
419	int device = open(path->String(), O_RDONLY);
420	if (device >= 0) {
421		fFileHandle = device;
422		fDrivePath = path;
423		fDriveIndex = drive;
424		return true;
425	}
426
427	return false;
428}
429
430
431const char *
432CDAudioDevice::GetDrivePath() const
433{
434	if (!fDrivePath)
435		return NULL;
436
437	return fDrivePath->String();
438}
439
440
441int32
442CDAudioDevice::_FindDrives(const char *path)
443{
444	BDirectory dir(path);
445
446	if (dir.InitCheck() != B_OK)
447		return B_ERROR;
448
449	dir.Rewind();
450
451	BEntry entry;
452	while (dir.GetNextEntry(&entry) >= 0) {
453		BPath path;
454		const char *name;
455		entry_ref e;
456
457		if (entry.GetPath(&path) != B_OK)
458			continue;
459
460		name = path.Path();
461		if (entry.GetRef(&e) != B_OK)
462			continue;
463
464		if (entry.IsDirectory()) {
465			// ignore floppy -- it is not silent
466			if (strcmp(e.name, "floppy") == 0)
467				continue;
468			else if (strcmp(e.name, "ata") == 0)
469				continue;
470
471			// Note that if we check for the count here, we could
472			// just search for one drive. However, we want to find *all* drives
473			// that are available, so we keep searching even if we've found one
474			_FindDrives(name);
475
476		} else {
477			int devfd;
478			device_geometry g;
479
480			// ignore partitions
481			if (strcmp(e.name, "raw") != 0)
482				continue;
483
484			devfd = open(name, O_RDONLY);
485			if (devfd < 0)
486				continue;
487
488			if (ioctl(devfd, B_GET_GEOMETRY, &g, sizeof(g)) >= 0) {
489				if (g.device_type == B_CD)
490					fDriveList.AddItem(new BString(name));
491			}
492			close(devfd);
493		}
494	}
495	return fDriveList.CountItems();
496}
497
498
499bool
500CDAudioDevice::GetTime(CDAudioTime &track, CDAudioTime &disc)
501{
502	scsi_position pos;
503
504	// Sanity check
505	status_t media_status = B_DEV_NO_MEDIA;
506	ioctl(fFileHandle, B_GET_MEDIA_STATUS, &media_status, sizeof(media_status));
507	if (media_status != B_OK)
508		return false;
509
510	status_t result = ioctl(fFileHandle, B_SCSI_GET_POSITION, &pos);
511
512	if (result != B_OK)
513		return false;
514
515	if ((!pos.position[1]) || (pos.position[1] >= 0x13) ||
516		((pos.position[1] == 0x12) && (!pos.position[6]))) {
517		// This indicates that we have a CD, but we are stopped.
518		return false;
519	}
520
521	disc.SetMinutes(pos.position[9]);
522	disc.SetSeconds(pos.position[10]);
523	track.SetMinutes(pos.position[13]);
524	track.SetSeconds(pos.position[14]);
525	return true;
526}
527
528
529bool
530CDAudioDevice::GetTimeForTrack(const int16 &index, CDAudioTime &track)
531{
532	scsi_toc toc;
533	status_t result = ioctl(fFileHandle, B_SCSI_GET_TOC, &toc);
534
535	if (result != B_OK)
536		return false;
537
538	int16 trackcount = toc.toc_data[3] - toc.toc_data[2] + 1;
539
540	if (index < 1 || index > trackcount)
541		return false;
542
543	TrackDescriptor *desc = (TrackDescriptor*)&(toc.toc_data[4]);
544
545	int32 tracktime = (desc[index].min * 60) + desc[index].sec;
546
547	tracktime -= (desc[index - 1].min * 60) + desc[index - 1].sec;
548	track.SetMinutes(tracktime / 60);
549	track.SetSeconds(tracktime % 60);
550
551	return true;
552}
553
554
555bool
556CDAudioDevice::GetTimeForDisc(CDAudioTime &disc)
557{
558	scsi_toc toc;
559	status_t result = ioctl(fFileHandle, B_SCSI_GET_TOC, &toc);
560
561	if (result != B_OK)
562		return false;
563
564	int16 trackcount = toc.toc_data[3] - toc.toc_data[2] + 1;
565	TrackDescriptor *desc = (TrackDescriptor*)&(toc.toc_data[4]);
566
567	disc.SetMinutes(desc[trackcount].min);
568	disc.SetSeconds(desc[trackcount].sec);
569
570	return true;
571}
572
573
574int32
575CDAudioDevice::GetDiscID()
576{
577	// Read the disc
578	scsi_toc toc;
579	status_t result = ioctl(fFileHandle, B_SCSI_GET_TOC, &toc);
580
581	if (result != B_OK)
582		return -1;
583
584
585	int32 id, numTracks;
586	BString frameOffsetsString;
587
588	ConvertedToc tocData[100];
589
590	// figure out the disc ID
591	for (int index = 0; index < 100; index++) {
592		tocData[index].min = toc.toc_data[9 + 8 * index];
593		tocData[index].sec = toc.toc_data[10 + 8 * index];
594		tocData[index].frame = toc.toc_data[11 + 8 * index];
595	}
596	numTracks = toc.toc_data[3] - toc.toc_data[2] + 1;
597
598	int32 sum1 = 0;
599	int32 sum2 = 0;
600	for (int index = 0; index < numTracks; index++) {
601		sum1 += cddb_sum((tocData[index].min * 60) + tocData[index].sec);
602
603		// the following is probably running over too far
604		sum2 +=	(tocData[index + 1].min * 60 + tocData[index + 1].sec) -
605			(tocData[index].min * 60 + tocData[index].sec);
606	}
607	id = ((sum1 % 0xff) << 24) + (sum2 << 8) + numTracks;
608
609	return id;
610}
611
612
613bool
614CDAudioDevice::IsDataTrack(const int16 &track)
615{
616	scsi_toc toc;
617	status_t result = ioctl(fFileHandle, B_SCSI_GET_TOC, &toc);
618
619	if (result != B_OK)
620		return false;
621
622	TrackDescriptor *trackindex = (TrackDescriptor*) &(toc.toc_data[4]);
623	if (track > toc.toc_data[3])
624		return false;
625
626	// At least under R5, the SCSI CD drive has each legitimate audio track
627	// have a value of 0x10. Data tracks have a value of 0x14;
628	if (trackindex[track].adr_control & 4)
629		return true;
630
631	return false;
632}
633