1 /* 2 SDL - Simple DirectMedia Layer 3 Copyright (C) 1997-2006 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 OSStatus err = noErr; 267 HParamBlockRec pb; 268 GetVolParmsInfoBuffer volParmsInfo; 269 270 if (fakeCD) 271 return "Fake CD-ROM Device"; 272 273 pb.ioParam.ioNamePtr = NULL; 274 pb.ioParam.ioVRefNum = volumes[drive]; 275 pb.ioParam.ioBuffer = (Ptr)&volParmsInfo; 276 pb.ioParam.ioReqCount = (SInt32)sizeof(volParmsInfo); 277 err = PBHGetVolParmsSync(&pb); 278 279 if (err != noErr) { 280 SDL_SetError ("PBHGetVolParmsSync returned %d", err); 281 return NULL; 282 } 283 284 return volParmsInfo.vMDeviceID; 285 } 286 287 /* Open the "device" */ 288 static int SDL_SYS_CDOpen (int drive) 289 { 290 /* Only allow 1 device to be open */ 291 if (currentDrive >= 0) { 292 SDL_SetError ("Only one cdrom is supported"); 293 return -1; 294 } 295 else 296 currentDrive = drive; 297 298 return drive; 299 } 300 301 /* Get the table of contents */ 302 static int SDL_SYS_CDGetTOC (SDL_CD *cdrom) 303 { 304 if (fakeCD) { 305 SDL_SetError (kErrorFakeDevice); 306 return -1; 307 } 308 309 if (didReadTOC) { 310 cdrom->numtracks = cacheTOCNumTracks; 311 return 0; 312 } 313 314 315 ReadTOCData (volumes[cdrom->id], cdrom); 316 didReadTOC = SDL_TRUE; 317 cacheTOCNumTracks = cdrom->numtracks; 318 319 return 0; 320 } 321 322 /* Get CD-ROM status */ 323 static CDstatus SDL_SYS_CDStatus (SDL_CD *cdrom, int *position) 324 { 325 if (position) { 326 int trackFrame; 327 328 Lock (); 329 trackFrame = GetCurrentFrame (); 330 Unlock (); 331 332 *position = cdrom->track[currentTrack].offset + trackFrame; 333 } 334 335 return status; 336 } 337 338 /* Start playback */ 339 static int SDL_SYS_CDPlay(SDL_CD *cdrom, int start, int length) 340 { 341 int startFrame, stopFrame; 342 FSRef *ref; 343 344 if (fakeCD) { 345 SDL_SetError (kErrorFakeDevice); 346 return -1; 347 } 348 349 Lock(); 350 351 if (LoadTracks (cdrom) < 0) 352 return -2; 353 354 if (PauseFile () < 0) 355 return -3; 356 357 if (ReleaseFile () < 0) 358 return -4; 359 360 ref = GetFileForOffset (cdrom, start, length, &startFrame, &stopFrame); 361 if (ref == NULL) { 362 SDL_SetError ("SDL_SYS_CDPlay: No file for start=%d, length=%d", start, length); 363 return -5; 364 } 365 366 if (LoadFile (ref, startFrame, stopFrame) < 0) 367 return -6; 368 369 SetCompletionProc (CompletionProc, cdrom); 370 371 if (PlayFile () < 0) 372 return -7; 373 374 status = CD_PLAYING; 375 376 Unlock(); 377 378 return 0; 379 } 380 381 /* Pause playback */ 382 static int SDL_SYS_CDPause(SDL_CD *cdrom) 383 { 384 if (fakeCD) { 385 SDL_SetError (kErrorFakeDevice); 386 return -1; 387 } 388 389 Lock (); 390 391 if (PauseFile () < 0) { 392 Unlock (); 393 return -2; 394 } 395 396 status = CD_PAUSED; 397 398 Unlock (); 399 400 return 0; 401 } 402 403 /* Resume playback */ 404 static int SDL_SYS_CDResume(SDL_CD *cdrom) 405 { 406 if (fakeCD) { 407 SDL_SetError (kErrorFakeDevice); 408 return -1; 409 } 410 411 Lock (); 412 413 if (PlayFile () < 0) { 414 Unlock (); 415 return -2; 416 } 417 418 status = CD_PLAYING; 419 420 Unlock (); 421 422 return 0; 423 } 424 425 /* Stop playback */ 426 static int SDL_SYS_CDStop(SDL_CD *cdrom) 427 { 428 if (fakeCD) { 429 SDL_SetError (kErrorFakeDevice); 430 return -1; 431 } 432 433 Lock (); 434 435 if (PauseFile () < 0) { 436 Unlock (); 437 return -2; 438 } 439 440 if (ReleaseFile () < 0) { 441 Unlock (); 442 return -3; 443 } 444 445 status = CD_STOPPED; 446 447 Unlock (); 448 449 return 0; 450 } 451 452 /* Eject the CD-ROM (Unmount the volume) */ 453 static int SDL_SYS_CDEject(SDL_CD *cdrom) 454 { 455 OSStatus err; 456 pid_t dissenter; 457 458 if (fakeCD) { 459 SDL_SetError (kErrorFakeDevice); 460 return -1; 461 } 462 463 Lock (); 464 465 if (PauseFile () < 0) { 466 Unlock (); 467 return -2; 468 } 469 470 if (ReleaseFile () < 0) { 471 Unlock (); 472 return -3; 473 } 474 475 status = CD_STOPPED; 476 477 /* Eject the volume */ 478 err = FSEjectVolumeSync(volumes[cdrom->id], kNilOptions, &dissenter); 479 480 if (err != noErr) { 481 Unlock (); 482 SDL_SetError ("PBUnmountVol returned %d", err); 483 return -4; 484 } 485 486 status = CD_TRAYEMPTY; 487 488 /* Invalidate volume and track info */ 489 volumes[cdrom->id] = 0; 490 free (tracks[cdrom->id]); 491 tracks[cdrom->id] = NULL; 492 493 Unlock (); 494 495 return 0; 496 } 497 498 /* Close the CD-ROM */ 499 static void SDL_SYS_CDClose(SDL_CD *cdrom) 500 { 501 currentDrive = -1; 502 return; 503 } 504 505 #endif /* SDL_CDROM_MACOSX */ 506