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