e14743d1 |
1 | /* |
2 | SDL - Simple DirectMedia Layer |
3 | Copyright (C) 1997-2009 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@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 */ |