1 /* 2 SDL - Simple DirectMedia Layer 3 Copyright (C) 1997-2012 Sam Lantinga 4 5 This library is free software; you can redistribute it and/or 6 modify it under the terms of the GNU Lesser General Public 7 License as published by the Free Software Foundation; either 8 version 2.1 of the License, or (at your option) any later version. 9 10 This library is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 Lesser General Public License for more details. 14 15 You should have received a copy of the GNU Lesser General Public 16 License along with this library; if not, write to the Free Software 17 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 18 19 Sam Lantinga 20 slouken (at) libsdl.org 21 */ 22 #include "SDL_config.h" 23 24 #ifdef SDL_CDROM_MACOSX 25 26 #include "SDL_syscdrom_c.h" 27 28 #pragma mark -- Globals -- 29 30 static FSRef** tracks; 31 static FSVolumeRefNum* volumes; 32 static CDstatus status; 33 static int nextTrackFrame; 34 static int nextTrackFramesRemaining; 35 static int fakeCD; 36 static int currentTrack; 37 static int didReadTOC; 38 static int cacheTOCNumTracks; 39 static int currentDrive; /* Only allow 1 drive in use at a time */ 40 41 #pragma mark -- Prototypes -- 42 43 static const char *SDL_SYS_CDName (int drive); 44 static int SDL_SYS_CDOpen (int drive); 45 static int SDL_SYS_CDGetTOC (SDL_CD *cdrom); 46 static CDstatus SDL_SYS_CDStatus (SDL_CD *cdrom, int *position); 47 static int SDL_SYS_CDPlay (SDL_CD *cdrom, int start, int length); 48 static int SDL_SYS_CDPause (SDL_CD *cdrom); 49 static int SDL_SYS_CDResume (SDL_CD *cdrom); 50 static int SDL_SYS_CDStop (SDL_CD *cdrom); 51 static int SDL_SYS_CDEject (SDL_CD *cdrom); 52 static void SDL_SYS_CDClose (SDL_CD *cdrom); 53 54 #pragma mark -- Helper Functions -- 55 56 /* Read a list of tracks from the volume */ 57 static int LoadTracks (SDL_CD *cdrom) 58 { 59 /* Check if tracks are already loaded */ 60 if ( tracks[cdrom->id] != NULL ) 61 return 0; 62 63 /* Allocate memory for tracks */ 64 tracks[cdrom->id] = (FSRef*) SDL_calloc (1, sizeof(**tracks) * cdrom->numtracks); 65 if (tracks[cdrom->id] == NULL) { 66 SDL_OutOfMemory (); 67 return -1; 68 } 69 70 /* Load tracks */ 71 if (ListTrackFiles (volumes[cdrom->id], tracks[cdrom->id], cdrom->numtracks) < 0) 72 return -1; 73 74 return 0; 75 } 76 77 /* Find a file for a given start frame and length */ 78 static FSRef* GetFileForOffset (SDL_CD *cdrom, int start, int length, int *outStartFrame, int *outStopFrame) 79 { 80 int i; 81 82 for (i = 0; i < cdrom->numtracks; i++) { 83 84 if (cdrom->track[i].offset <= start && 85 start < (cdrom->track[i].offset + cdrom->track[i].length)) 86 break; 87 } 88 89 if (i == cdrom->numtracks) 90 return NULL; 91 92 currentTrack = i; 93 94 *outStartFrame = start - cdrom->track[i].offset; 95 96 if ((*outStartFrame + length) < cdrom->track[i].length) { 97 *outStopFrame = *outStartFrame + length; 98 length = 0; 99 nextTrackFrame = -1; 100 nextTrackFramesRemaining = -1; 101 } 102 else { 103 *outStopFrame = -1; 104 length -= cdrom->track[i].length - *outStartFrame; 105 nextTrackFrame = cdrom->track[i+1].offset; 106 nextTrackFramesRemaining = length; 107 } 108 109 return &tracks[cdrom->id][i]; 110 } 111 112 /* Setup another file for playback, or stop playback (called from another thread) */ 113 static void CompletionProc (SDL_CD *cdrom) 114 { 115 116 Lock (); 117 118 if (nextTrackFrame > 0 && nextTrackFramesRemaining > 0) { 119 120 /* Load the next file to play */ 121 int startFrame, stopFrame; 122 FSRef *file; 123 124 PauseFile (); 125 ReleaseFile (); 126 127 file = GetFileForOffset (cdrom, nextTrackFrame, 128 nextTrackFramesRemaining, &startFrame, &stopFrame); 129 130 if (file == NULL) { 131 status = CD_STOPPED; 132 Unlock (); 133 return; 134 } 135 136 LoadFile (file, startFrame, stopFrame); 137 138 SetCompletionProc (CompletionProc, cdrom); 139 140 PlayFile (); 141 } 142 else { 143 144 /* Release the current file */ 145 PauseFile (); 146 ReleaseFile (); 147 status = CD_STOPPED; 148 } 149 150 Unlock (); 151 } 152 153 154 #pragma mark -- Driver Functions -- 155 156 /* Initialize */ 157 int SDL_SYS_CDInit (void) 158 { 159 /* Initialize globals */ 160 volumes = NULL; 161 tracks = NULL; 162 status = CD_STOPPED; 163 nextTrackFrame = -1; 164 nextTrackFramesRemaining = -1; 165 fakeCD = SDL_FALSE; 166 currentTrack = -1; 167 didReadTOC = SDL_FALSE; 168 cacheTOCNumTracks = -1; 169 currentDrive = -1; 170 171 /* Fill in function pointers */ 172 SDL_CDcaps.Name = SDL_SYS_CDName; 173 SDL_CDcaps.Open = SDL_SYS_CDOpen; 174 SDL_CDcaps.GetTOC = SDL_SYS_CDGetTOC; 175 SDL_CDcaps.Status = SDL_SYS_CDStatus; 176 SDL_CDcaps.Play = SDL_SYS_CDPlay; 177 SDL_CDcaps.Pause = SDL_SYS_CDPause; 178 SDL_CDcaps.Resume = SDL_SYS_CDResume; 179 SDL_CDcaps.Stop = SDL_SYS_CDStop; 180 SDL_CDcaps.Eject = SDL_SYS_CDEject; 181 SDL_CDcaps.Close = SDL_SYS_CDClose; 182 183 /* 184 Read the list of "drives" 185 186 This is currently a hack that infers drives from 187 mounted audio CD volumes, rather than 188 actual CD-ROM devices - which means it may not 189 act as expected sometimes. 190 */ 191 192 /* Find out how many cd volumes are mounted */ 193 SDL_numcds = DetectAudioCDVolumes (NULL, 0); 194 195 /* 196 If there are no volumes, fake a cd device 197 so tray empty can be reported. 198 */ 199 if (SDL_numcds == 0) { 200 201 fakeCD = SDL_TRUE; 202 SDL_numcds = 1; 203 status = CD_TRAYEMPTY; 204 205 return 0; 206 } 207 208 /* Allocate space for volumes */ 209 volumes = (FSVolumeRefNum*) SDL_calloc (1, sizeof(*volumes) * SDL_numcds); 210 if (volumes == NULL) { 211 SDL_OutOfMemory (); 212 return -1; 213 } 214 215 /* Allocate space for tracks */ 216 tracks = (FSRef**) SDL_calloc (1, sizeof(*tracks) * (SDL_numcds + 1)); 217 if (tracks == NULL) { 218 SDL_OutOfMemory (); 219 return -1; 220 } 221 222 /* Mark the end of the tracks array */ 223 tracks[ SDL_numcds ] = (FSRef*)-1; 224 225 /* 226 Redetect, now save all volumes for later 227 Update SDL_numcds just in case it changed 228 */ 229 { 230 int numVolumes = SDL_numcds; 231 232 SDL_numcds = DetectAudioCDVolumes (volumes, numVolumes); 233 234 /* If more cds suddenly show up, ignore them */ 235 if (SDL_numcds > numVolumes) { 236 SDL_SetError ("Some CD's were added but they will be ignored"); 237 SDL_numcds = numVolumes; 238 } 239 } 240 241 return 0; 242 } 243 244 /* Shutdown and cleanup */ 245 void SDL_SYS_CDQuit(void) 246 { 247 ReleaseFile(); 248 249 if (volumes != NULL) 250 free (volumes); 251 252 if (tracks != NULL) { 253 254 FSRef **ptr; 255 for (ptr = tracks; *ptr != (FSRef*)-1; ptr++) 256 if (*ptr != NULL) 257 free (*ptr); 258 259 free (tracks); 260 } 261 } 262 263 /* Get the Unix disk name of the volume */ 264 static const char *SDL_SYS_CDName (int drive) 265 { 266 /* 267 * !!! FIXME: PBHGetVolParmsSync() is gone in 10.6, 268 * !!! FIXME: replaced with FSGetVolumeParms(), which 269 * !!! FIXME: isn't available before 10.5. :/ 270 */ 271 return "Mac OS X CD-ROM Device"; 272 273 #if 0 274 OSStatus err = noErr; 275 HParamBlockRec pb; 276 GetVolParmsInfoBuffer volParmsInfo; 277 278 if (fakeCD) 279 return "Fake CD-ROM Device"; 280 281 pb.ioParam.ioNamePtr = NULL; 282 pb.ioParam.ioVRefNum = volumes[drive]; 283 pb.ioParam.ioBuffer = (Ptr)&volParmsInfo; 284 pb.ioParam.ioReqCount = (SInt32)sizeof(volParmsInfo); 285 err = PBHGetVolParmsSync(&pb); 286 287 if (err != noErr) { 288 SDL_SetError ("PBHGetVolParmsSync returned %d", err); 289 return NULL; 290 } 291 292 return volParmsInfo.vMDeviceID; 293 #endif 294 } 295 296 /* Open the "device" */ 297 static int SDL_SYS_CDOpen (int drive) 298 { 299 /* Only allow 1 device to be open */ 300 if (currentDrive >= 0) { 301 SDL_SetError ("Only one cdrom is supported"); 302 return -1; 303 } 304 else 305 currentDrive = drive; 306 307 return drive; 308 } 309 310 /* Get the table of contents */ 311 static int SDL_SYS_CDGetTOC (SDL_CD *cdrom) 312 { 313 if (fakeCD) { 314 SDL_SetError (kErrorFakeDevice); 315 return -1; 316 } 317 318 if (didReadTOC) { 319 cdrom->numtracks = cacheTOCNumTracks; 320 return 0; 321 } 322 323 324 ReadTOCData (volumes[cdrom->id], cdrom); 325 didReadTOC = SDL_TRUE; 326 cacheTOCNumTracks = cdrom->numtracks; 327 328 return 0; 329 } 330 331 /* Get CD-ROM status */ 332 static CDstatus SDL_SYS_CDStatus (SDL_CD *cdrom, int *position) 333 { 334 if (position) { 335 int trackFrame; 336 337 Lock (); 338 trackFrame = GetCurrentFrame (); 339 Unlock (); 340 341 *position = cdrom->track[currentTrack].offset + trackFrame; 342 } 343 344 return status; 345 } 346 347 /* Start playback */ 348 static int SDL_SYS_CDPlay(SDL_CD *cdrom, int start, int length) 349 { 350 int startFrame, stopFrame; 351 FSRef *ref; 352 353 if (fakeCD) { 354 SDL_SetError (kErrorFakeDevice); 355 return -1; 356 } 357 358 Lock(); 359 360 if (LoadTracks (cdrom) < 0) 361 return -2; 362 363 if (PauseFile () < 0) 364 return -3; 365 366 if (ReleaseFile () < 0) 367 return -4; 368 369 ref = GetFileForOffset (cdrom, start, length, &startFrame, &stopFrame); 370 if (ref == NULL) { 371 SDL_SetError ("SDL_SYS_CDPlay: No file for start=%d, length=%d", start, length); 372 return -5; 373 } 374 375 if (LoadFile (ref, startFrame, stopFrame) < 0) 376 return -6; 377 378 SetCompletionProc (CompletionProc, cdrom); 379 380 if (PlayFile () < 0) 381 return -7; 382 383 status = CD_PLAYING; 384 385 Unlock(); 386 387 return 0; 388 } 389 390 /* Pause playback */ 391 static int SDL_SYS_CDPause(SDL_CD *cdrom) 392 { 393 if (fakeCD) { 394 SDL_SetError (kErrorFakeDevice); 395 return -1; 396 } 397 398 Lock (); 399 400 if (PauseFile () < 0) { 401 Unlock (); 402 return -2; 403 } 404 405 status = CD_PAUSED; 406 407 Unlock (); 408 409 return 0; 410 } 411 412 /* Resume playback */ 413 static int SDL_SYS_CDResume(SDL_CD *cdrom) 414 { 415 if (fakeCD) { 416 SDL_SetError (kErrorFakeDevice); 417 return -1; 418 } 419 420 Lock (); 421 422 if (PlayFile () < 0) { 423 Unlock (); 424 return -2; 425 } 426 427 status = CD_PLAYING; 428 429 Unlock (); 430 431 return 0; 432 } 433 434 /* Stop playback */ 435 static int SDL_SYS_CDStop(SDL_CD *cdrom) 436 { 437 if (fakeCD) { 438 SDL_SetError (kErrorFakeDevice); 439 return -1; 440 } 441 442 Lock (); 443 444 if (PauseFile () < 0) { 445 Unlock (); 446 return -2; 447 } 448 449 if (ReleaseFile () < 0) { 450 Unlock (); 451 return -3; 452 } 453 454 status = CD_STOPPED; 455 456 Unlock (); 457 458 return 0; 459 } 460 461 /* Eject the CD-ROM (Unmount the volume) */ 462 static int SDL_SYS_CDEject(SDL_CD *cdrom) 463 { 464 OSStatus err; 465 pid_t dissenter; 466 467 if (fakeCD) { 468 SDL_SetError (kErrorFakeDevice); 469 return -1; 470 } 471 472 Lock (); 473 474 if (PauseFile () < 0) { 475 Unlock (); 476 return -2; 477 } 478 479 if (ReleaseFile () < 0) { 480 Unlock (); 481 return -3; 482 } 483 484 status = CD_STOPPED; 485 486 /* Eject the volume */ 487 err = FSEjectVolumeSync(volumes[cdrom->id], kNilOptions, &dissenter); 488 489 if (err != noErr) { 490 Unlock (); 491 SDL_SetError ("PBUnmountVol returned %d", err); 492 return -4; 493 } 494 495 status = CD_TRAYEMPTY; 496 497 /* Invalidate volume and track info */ 498 volumes[cdrom->id] = 0; 499 free (tracks[cdrom->id]); 500 tracks[cdrom->id] = NULL; 501 502 Unlock (); 503 504 return 0; 505 } 506 507 /* Close the CD-ROM */ 508 static void SDL_SYS_CDClose(SDL_CD *cdrom) 509 { 510 currentDrive = -1; 511 return; 512 } 513 514 #endif /* SDL_CDROM_MACOSX */ 515